Sync
This commit is contained in:
parent
94197780e1
commit
2977f09245
32 changed files with 763 additions and 936 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -3,3 +3,6 @@ db.sqlite
|
|||
.env
|
||||
.lapce/
|
||||
*/tmp.*
|
||||
|
||||
config.toml
|
||||
.air.toml
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
package ap
|
||||
|
||||
import "net/url"
|
||||
|
||||
type IdType struct {
|
||||
Id *url.URL
|
||||
}
|
||||
|
||||
type ValueType[T any] struct {
|
||||
Value T
|
||||
}
|
||||
|
||||
type Media struct {
|
||||
Type url.URL
|
||||
MediaType string
|
||||
Url url.URL
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
package ap
|
||||
|
||||
const (
|
||||
KEY_ID = "@id" // Value of type string
|
||||
KEY_TYPE = "@type" // Value of type string slice / activitystreams object url slice
|
||||
KEY_VALUE = "@value" // Could be any type really
|
||||
)
|
||||
|
||||
const (
|
||||
KEY_MASTO_DEVICES = "http://joinmastodon.org/ns#devices"
|
||||
KEY_MASTO_DISCOVERABLE = "http://joinmastodon.org/ns#discoverable"
|
||||
KEY_MASTO_FEATURED = "http://joinmastodon.org/ns#featured"
|
||||
KEY_MASTO_FEATURED_TAGS = "http://joinmastodon.org/ns#featuredTags"
|
||||
KEY_MASTO_INDEXABLE = "http://joinmastodon.org/ns#indexable"
|
||||
KEY_MASTO_MEMORIAL = "http://joinmastodon.org/ns#memorial"
|
||||
)
|
||||
|
||||
const (
|
||||
KEY_W3_INBOX = "http://www.w3.org/ns/ldp#inbox"
|
||||
KEY_W3_SECURITY_PUBLICKEY = "https://w3id.org/security#publicKey"
|
||||
KEY_W3_SECURITY_OWNER = "https://w3id.org/security#owner"
|
||||
KEY_W3_SECURITY_PUBLICKEYPEM = "https://w3id.org/security#publicKeyPem"
|
||||
)
|
||||
|
||||
const (
|
||||
KEY_ACTIVITYSTREAMS_ALSOKNOWNAS = "https://www.w3.org/ns/activitystreams#alsoKnownAs"
|
||||
KEY_ACTIVITYSTREAMS_ATTACHMENTS = "https://www.w3.org/ns/activitystreams#attachment"
|
||||
KEY_ACTIVITYSTREAMS_NAME = "https://www.w3.org/ns/activitystreams#name"
|
||||
KEY_ACTIVITYSTREAMS_ENDPOINTS = "https://www.w3.org/ns/activitystreams#endpoints"
|
||||
KEY_ACTIVITYSTREAMS_SHAREDINBOX = "https://www.w3.org/ns/activitystreams#sharedInbox"
|
||||
KEY_ACTIVITYSTREAMS_FOLLOWERS = "https://www.w3.org/ns/activitystreams#followers"
|
||||
KEY_ACTIVITYSTREAMS_FOLLOWING = "https://www.w3.org/ns/activitystreams#following"
|
||||
KEY_ACTIVITYSTREAMS_ICON = "https://www.w3.org/ns/activitystreams#icon"
|
||||
KEY_ACTIVITYSTREAMS_MEDIATYPE = "https://www.w3.org/ns/activitystreams#mediaType"
|
||||
KEY_ACTIVITYSTREAMS_URL = "https://www.w3.org/ns/activitystreams#url"
|
||||
KEY_ACTIVITYSTREAMS_IMAGE = "https://www.w3.org/ns/activitystreams#image"
|
||||
KEY_ACTIVITYSTREAMS_RESTRICTED_FOLLOW = "https://www.w3.org/ns/activitystreams#manuallyApprovesFollowers"
|
||||
KEY_ACTIVITYSTREAMS_OUTBOX = "https://www.w3.org/ns/activitystreams#outbox"
|
||||
KEY_ACTIVITYSTREAMS_PREFFEREDUSERNAME = "https://www.w3.org/ns/activitystreams#preferredUsername"
|
||||
KEY_ACTIVITYSTREAMS_PUBLISHED = "https://www.w3.org/ns/activitystreams#published"
|
||||
KEY_ACTIVITYSTREAMS_SUMMARY = "https://www.w3.org/ns/activitystreams#summary"
|
||||
KEY_ACTIVITYSTREAMS_TAG = "https://www.w3.org/ns/activitystreams#tag"
|
||||
KEY_ACTIVITYSTREAMS_CC = "https://www.w3.org/ns/activitystreams#cc"
|
||||
KEY_ACTIVITYSTREAMS_TO = "https://www.w3.org/ns/activitystreams#to"
|
||||
KEY_ACTIVITYSTREAMS_OBJECT = "https://www.w3.org/ns/activitystreams#object"
|
||||
|
||||
// Object types (I think)
|
||||
// Those are values the object type can have
|
||||
|
||||
KEY_ACTIVITYSTREAMS_ACTOR = "https://www.w3.org/ns/activitystreams#actor"
|
||||
KEY_ACTIVITYSTREAMS_FOLLOW = "https://www.w3.org/ns/activitystreams#Follow"
|
||||
KEY_ACTIVITYSTREAMS_PERSON = "https://www.w3.org/ns/activitystreams#Person"
|
||||
KEY_ACTIVITYSTREAMS_CREATE = "https://www.w3.org/ns/activitystreams#Create"
|
||||
)
|
||||
|
||||
const (
|
||||
KEY_SCHEMA_VALUE = "http://schema.org#value"
|
||||
)
|
|
@ -1,49 +0,0 @@
|
|||
package ap
|
||||
|
||||
// Just steal this one from sharkey
|
||||
// Also use this one for all AP objects for now
|
||||
// Can make a function to extract context from an expanded object later
|
||||
// TODO: Consider if such a function is worth it or if it would hinder learning
|
||||
var allContext = map[string]any{
|
||||
"@context": []any{
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
map[string]any{
|
||||
"fedibird": "http://fedibird.com/ns#",
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"schema": "http://schema.org#",
|
||||
"misskey": "https://misskey-hub.net/ns#",
|
||||
"firefish": "https://joinfirefish.org/ns#",
|
||||
"sharkey": "https://joinsharkey.org/ns#",
|
||||
"vcard": "http://www.w3.org/2006/vcard/ns#",
|
||||
|
||||
"Key": "sec:Key",
|
||||
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"sensitive": "as:sensitive",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"quoteUrl": "as:quoteUrl",
|
||||
|
||||
"quoteUri": "fedibird:quoteUri",
|
||||
|
||||
"Emoji": "toot:Emoji",
|
||||
"featured": "toot:featured",
|
||||
"discoverable": "toot:discoverable",
|
||||
|
||||
"PropertyValue": "schema:PropertyValue",
|
||||
"value": "schema:value",
|
||||
|
||||
"_misskey_content": "misskey:_misskey_content",
|
||||
"_misskey_quote": "misskey:_misskey_quote",
|
||||
"_misskey_reaction": "misskey:_misskey_reaction",
|
||||
"_misskey_votes": "misskey:_misskey_votes",
|
||||
"_misskey_summary": "misskey:_misskey_summary",
|
||||
"isCat": "misskey:isCat",
|
||||
|
||||
"speakAsCat": "firefish:speakAsCat",
|
||||
|
||||
"backgroundUrl": "sharkey:backgroundUrl",
|
||||
"listenbrainz": "sharkey:listenbrainz",
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,185 +0,0 @@
|
|||
[
|
||||
{
|
||||
"@id": "https://mastodon.social/users/Gargron",
|
||||
"@type": [
|
||||
"https://www.w3.org/ns/activitystreams#Person"
|
||||
],
|
||||
"http://joinmastodon.org/ns#devices": [
|
||||
{
|
||||
"@id": "https://mastodon.social/users/Gargron/collections/devices"
|
||||
}
|
||||
],
|
||||
"http://joinmastodon.org/ns#discoverable": [
|
||||
{
|
||||
"@value": true
|
||||
}
|
||||
],
|
||||
"http://joinmastodon.org/ns#featured": [
|
||||
{
|
||||
"@id": "https://mastodon.social/users/Gargron/collections/featured"
|
||||
}
|
||||
],
|
||||
"http://joinmastodon.org/ns#featuredTags": [
|
||||
{
|
||||
"@id": "https://mastodon.social/users/Gargron/collections/tags"
|
||||
}
|
||||
],
|
||||
"http://joinmastodon.org/ns#indexable": [
|
||||
{
|
||||
"@value": true
|
||||
}
|
||||
],
|
||||
"http://joinmastodon.org/ns#memorial": [
|
||||
{
|
||||
"@value": false
|
||||
}
|
||||
],
|
||||
"http://www.w3.org/ns/ldp#inbox": [
|
||||
{
|
||||
"@id": "https://mastodon.social/users/Gargron/inbox"
|
||||
}
|
||||
],
|
||||
"https://w3id.org/security#publicKey": [
|
||||
{
|
||||
"@id": "https://mastodon.social/users/Gargron#main-key",
|
||||
"https://w3id.org/security#owner": [
|
||||
{
|
||||
"@id": "https://mastodon.social/users/Gargron"
|
||||
}
|
||||
],
|
||||
"https://w3id.org/security#publicKeyPem": [
|
||||
{
|
||||
"@value": "-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvXc4vkECU2/CeuSo1wtn Foim94Ne1jBMYxTZ9wm2YTdJq1oiZKif06I2fOqDzY/4q/S9uccrE9Bkajv1dnkO Vm31QjWlhVpSKynVxEWjVBO5Ienue8gND0xvHIuXf87o61poqjEoepvsQFElA5ym ovljWGSA/jpj7ozygUZhCXtaS2W5AD5tnBQUpcO0lhItYPYTjnmzcc4y2NbJV8hz 2s2G8qKv8fyimE23gY1XrPJg+cRF+g4PqFXujjlJ7MihD9oqtLGxbu7o1cifTn3x BfIdPythWu5b4cujNsB3m3awJjVmx+MHQ9SugkSIYXV0Ina77cTNS0M2PYiH1PFR TwIDAQAB -----END PUBLIC KEY----- "
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#alsoKnownAs": [
|
||||
{
|
||||
"@id": "https://tooting.ai/users/Gargron"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#attachment": [
|
||||
{
|
||||
"@type": [
|
||||
"http://schema.org#PropertyValue"
|
||||
],
|
||||
"http://schema.org#value": [
|
||||
{
|
||||
"@value": "\u003ca href=\"https://www.patreon.com/mastodon\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"\u003e\u003cspan class=\"invisible\"\u003ehttps://www.\u003c/span\u003e\u003cspan class=\"\"\u003epatreon.com/mastodon\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#name": [
|
||||
{
|
||||
"@value": "Patreon"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"@type": [
|
||||
"http://schema.org#PropertyValue"
|
||||
],
|
||||
"http://schema.org#value": [
|
||||
{
|
||||
"@value": "\u003ca href=\"https://github.com/Gargron\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003egithub.com/Gargron\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#name": [
|
||||
{
|
||||
"@value": "GitHub"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#endpoints": [
|
||||
{
|
||||
"https://www.w3.org/ns/activitystreams#sharedInbox": [
|
||||
{
|
||||
"@id": "https://mastodon.social/inbox"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#followers": [
|
||||
{
|
||||
"@id": "https://mastodon.social/users/Gargron/followers"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#following": [
|
||||
{
|
||||
"@id": "https://mastodon.social/users/Gargron/following"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#icon": [
|
||||
{
|
||||
"@type": [
|
||||
"https://www.w3.org/ns/activitystreams#Image"
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#mediaType": [
|
||||
{
|
||||
"@value": "image/png"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#url": [
|
||||
{
|
||||
"@id": "https://files.mastodon.social/accounts/avatars/000/000/001/original/a0a49d80c3de5f75.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#image": [
|
||||
{
|
||||
"@type": [
|
||||
"https://www.w3.org/ns/activitystreams#Image"
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#mediaType": [
|
||||
{
|
||||
"@value": "image/jpeg"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#url": [
|
||||
{
|
||||
"@id": "https://files.mastodon.social/accounts/headers/000/000/001/original/d13e4417706a5fec.jpg"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#manuallyApprovesFollowers": [
|
||||
{
|
||||
"@value": false
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#name": [
|
||||
{
|
||||
"@value": "Eugen Rochko"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#outbox": [
|
||||
{
|
||||
"@id": "https://mastodon.social/users/Gargron/outbox"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#preferredUsername": [
|
||||
{
|
||||
"@value": "Gargron"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#published": [
|
||||
{
|
||||
"@type": "http://www.w3.org/2001/XMLSchema#dateTime",
|
||||
"@value": "2016-03-16T00:00:00Z"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#summary": [
|
||||
{
|
||||
"@value": "\u003cp\u003eFounder of \u003cspan class=\"h-card\" translate=\"no\"\u003e\u003ca href=\"https://mastodon.social/@Mastodon\" class=\"u-url mention\"\u003e@\u003cspan\u003eMastodon\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e. Film photography, prog metal, Dota 2. Likes all things analog.\u003c/p\u003e"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#tag": [],
|
||||
"https://www.w3.org/ns/activitystreams#url": [
|
||||
{
|
||||
"@id": "https://mastodon.social/@Gargron"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -1,18 +0,0 @@
|
|||
[
|
||||
{
|
||||
"@id": "https://activitypub.academy/1e8a5594-eff7-4946-86fe-84d82d0a14ae",
|
||||
"@type": [
|
||||
"https://www.w3.org/ns/activitystreams#Follow"
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#actor": [
|
||||
{
|
||||
"@id": "https://activitypub.academy/users/dadacio_dashdorrol"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#object": [
|
||||
{
|
||||
"@id": "https://woem.men/users/9n39zo1rfckr00q5"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -1,41 +0,0 @@
|
|||
[
|
||||
{
|
||||
"@id": "https://woem.men/notes/9ttp29lhge2u0454",
|
||||
"@type": [
|
||||
"https://www.w3.org/ns/activitystreams#Note"
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#attachment": [],
|
||||
"https://www.w3.org/ns/activitystreams#attributedTo": [
|
||||
{
|
||||
"@id": "https://woem.men/users/9n39zo1rfckr00q5"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#cc": [
|
||||
{
|
||||
"@id": "https://woem.men/users/9n39zo1rfckr00q5/followers"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#content": [
|
||||
{
|
||||
"@value": "\u003cp\u003eTest post, ignore\u003c/p\u003e"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#published": [
|
||||
{
|
||||
"@type": "http://www.w3.org/2001/XMLSchema#dateTime",
|
||||
"@value": "2024-05-28T08:22:59.861Z"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#sensitive": [
|
||||
{
|
||||
"@value": false
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#tag": [],
|
||||
"https://www.w3.org/ns/activitystreams#to": [
|
||||
{
|
||||
"@id": "https://www.w3.org/ns/activitystreams#Public"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -1,70 +0,0 @@
|
|||
[
|
||||
{
|
||||
"@id": "https://woem.men/notes/9ttp29lhge2u0454/activity",
|
||||
"@type": [
|
||||
"https://www.w3.org/ns/activitystreams#Create"
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#actor": [
|
||||
{
|
||||
"@id": "https://woem.men/users/9n39zo1rfckr00q5"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#cc": [
|
||||
{
|
||||
"@id": "https://woem.men/users/9n39zo1rfckr00q5/followers"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#object": [
|
||||
{
|
||||
"@id": "https://woem.men/notes/9ttp29lhge2u0454",
|
||||
"@type": [
|
||||
"https://www.w3.org/ns/activitystreams#Note"
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#attachment": [],
|
||||
"https://www.w3.org/ns/activitystreams#attributedTo": [
|
||||
{
|
||||
"@id": "https://woem.men/users/9n39zo1rfckr00q5"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#cc": [
|
||||
{
|
||||
"@id": "https://woem.men/users/9n39zo1rfckr00q5/followers"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#content": [
|
||||
{
|
||||
"@value": "\u003cp\u003eTest post, ignore\u003c/p\u003e"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#published": [
|
||||
{
|
||||
"@type": "http://www.w3.org/2001/XMLSchema#dateTime",
|
||||
"@value": "2024-05-28T08:22:59.861Z"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#sensitive": [
|
||||
{
|
||||
"@value": false
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#tag": [],
|
||||
"https://www.w3.org/ns/activitystreams#to": [
|
||||
{
|
||||
"@id": "https://www.w3.org/ns/activitystreams#Public"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#published": [
|
||||
{
|
||||
"@type": "http://www.w3.org/2001/XMLSchema#dateTime",
|
||||
"@value": "2024-05-28T08:22:59.861Z"
|
||||
}
|
||||
],
|
||||
"https://www.w3.org/ns/activitystreams#to": [
|
||||
{
|
||||
"@id": "https://www.w3.org/ns/activitystreams#Public"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -1,62 +0,0 @@
|
|||
package ap
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
ActivityStreamsAlsoKnownAs IdType
|
||||
ActivityStreamsAttachment struct {
|
||||
Type *url.URL
|
||||
Value string
|
||||
Name string
|
||||
}
|
||||
ActivityStreamsSharedInbox IdType
|
||||
ActivityStreamsFollowers IdType
|
||||
ActivityStreamsFollowing IdType
|
||||
ActivityStreamsImage Media
|
||||
ActivityStreamsIcon Media
|
||||
ActivityStreamsRestrictedFollow ValueType[bool]
|
||||
ActivityStreamsName ValueType[string]
|
||||
ActivityStreamsOutbox IdType
|
||||
ActivityStreamsPrefferedUsername ValueType[string]
|
||||
ActivityStreamsPublished struct {
|
||||
Type string
|
||||
Value time.Time
|
||||
}
|
||||
ActivityStreamsSummary ValueType[string]
|
||||
ActivityStreamsUrl IdType
|
||||
// NOTE: I do not know if this is consistent at all. Do not trust yet
|
||||
ActivityStreamsTag struct {
|
||||
Type string
|
||||
Id string
|
||||
Name string
|
||||
}
|
||||
ActivityStreamsTo IdType
|
||||
ActivityStreamsCC IdType
|
||||
|
||||
ActivityStreamsActor IdType
|
||||
// NOTE: Technically, objects can have a LOT of data. I don't care. Treat them as ID type
|
||||
// Just fetch whatever the ID is later on separately and throw away anything else but the ID
|
||||
ActivityStreamsObject IdType
|
||||
ActivityStreamsFollow IdType
|
||||
)
|
||||
|
||||
type (
|
||||
MastoDevices IdType
|
||||
MastoDiscoverable ValueType[bool]
|
||||
MastoFeatured IdType
|
||||
MastoFeaturedTags IdType
|
||||
MastoIndexable ValueType[bool]
|
||||
MastoMemorial ValueType[bool]
|
||||
)
|
||||
|
||||
type (
|
||||
W3Inbox IdType
|
||||
W3SecurityPublicKey struct {
|
||||
Id *url.URL
|
||||
Owner *url.URL
|
||||
KeyPem string
|
||||
}
|
||||
)
|
27
ap/getRemoteUser.go
Normal file
27
ap/getRemoteUser.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package ap
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"gitlab.com/mstarongitlab/goap"
|
||||
)
|
||||
|
||||
func GetRemoteUser(fullHandle string) (goap.BaseApChain, error) {
|
||||
webfinger, err := GetAccountWebfinger(fullHandle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apUrl := ""
|
||||
for _, link := range webfinger.Links {
|
||||
if link.Relation == "self" {
|
||||
apUrl = *link.Href
|
||||
}
|
||||
}
|
||||
apRequest, err := http.NewRequest("GET", apUrl, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apRequest.Header.Add("Accept", "application/activity+json,application/ld+json,application/json")
|
||||
return nil, nil
|
||||
}
|
59
ap/parser.go
59
ap/parser.go
|
@ -1,59 +0,0 @@
|
|||
package ap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/piprate/json-gold/ld"
|
||||
)
|
||||
|
||||
type ApThing map[string]any
|
||||
|
||||
type RemoteDocumentLoader struct{}
|
||||
|
||||
// Try and parse a remote ActivityPub object into a more usable form
|
||||
// Result is a map[string]any where the keys defined in /ap/constants.go should be usable,
|
||||
// depending on the type of object
|
||||
// The general approach for fetching an object will be to fetch the main object
|
||||
// and only store the ID for sub-objects to fetch them later
|
||||
func ParseFromUrl(u *url.URL) (ApThing, error) {
|
||||
opts := ld.NewJsonLdOptions("")
|
||||
processor := ld.NewJsonLdProcessor()
|
||||
// TODO: Add custom document parser (copy default implementation) that includes verification
|
||||
|
||||
// Explanation:
|
||||
// Expansion removes the context from a document (json-ld activitypub data)
|
||||
// and turns every field into something along the lines of
|
||||
// "https://example.com/ns#ObjectType": <Insert data for that object here>
|
||||
// This makes it easier to deal with things as they now have a very consistent naming scheme
|
||||
// See /ap/constants.go for those
|
||||
|
||||
parsed, err := processor.Expand(u.String(), opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to process remote document: %w", err)
|
||||
}
|
||||
if len(parsed) == 0 {
|
||||
return nil, fmt.Errorf("document has a length of 0")
|
||||
}
|
||||
typed, ok := parsed[0].(ApThing)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("couldn't cast data to ApThing")
|
||||
}
|
||||
return typed, nil
|
||||
}
|
||||
|
||||
// Compact an AP object down into a compressed json-ld form
|
||||
// That compacted form should be accepted by all AP servers
|
||||
// It also handles context for any fields
|
||||
// Content should only use keys defined in /ap/constants.go though
|
||||
// Other things might get lost in translation
|
||||
func Compact(content map[string]any) ([]byte, error) {
|
||||
opts := ld.NewJsonLdOptions("")
|
||||
processor := ld.NewJsonLdProcessor()
|
||||
res, err := processor.Compact(content, allContext, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compact data: %w", err)
|
||||
}
|
||||
return json.Marshal(res)
|
||||
}
|
|
@ -1,172 +0,0 @@
|
|||
package ap
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Try and parse a value into an IdType
|
||||
// Returns nil if failed
|
||||
func TryParseIdType(rawIn any) *IdType {
|
||||
switch in := rawIn.(type) {
|
||||
case []any:
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
}
|
||||
m, ok := in[0].(map[string]any)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return TryParseIdType(m)
|
||||
case map[string]any:
|
||||
vRaw, ok := in[KEY_ID]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
v, ok := vRaw.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
u, err := url.Parse(v)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &IdType{
|
||||
Id: u,
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func TryParseValueType[T any](rawIn any) *ValueType[T] {
|
||||
switch in := rawIn.(type) {
|
||||
case map[string]any:
|
||||
vRaw, ok := in[KEY_ID]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
v, ok := vRaw.(T)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return &ValueType[T]{
|
||||
Value: v,
|
||||
}
|
||||
case []any:
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
}
|
||||
v, ok := in[0].(map[string]any)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return TryParseValueType[T](v)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func TryParseActivityStreamsPublicKey(rawIn any) *W3SecurityPublicKey {
|
||||
switch in := rawIn.(type) {
|
||||
case map[string]any:
|
||||
asIdType := TryParseIdType(in)
|
||||
if asIdType == nil {
|
||||
return nil
|
||||
}
|
||||
ownerType := TryParseIdType(in[KEY_W3_SECURITY_OWNER])
|
||||
if ownerType == nil {
|
||||
return nil
|
||||
}
|
||||
keyValue := TryParseValueType[string](in[KEY_W3_SECURITY_PUBLICKEYPEM])
|
||||
if keyValue == nil {
|
||||
return nil
|
||||
}
|
||||
return &W3SecurityPublicKey{
|
||||
Id: asIdType.Id,
|
||||
Owner: ownerType.Id,
|
||||
KeyPem: keyValue.Value,
|
||||
}
|
||||
case []any:
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
}
|
||||
return TryParseActivityStreamsPublicKey(in[0])
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func TryParseActivityStreamsAttachment(rawIn any) *ActivityStreamsAttachment {
|
||||
switch in := rawIn.(type) {
|
||||
case []any:
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
}
|
||||
return TryParseActivityStreamsAttachment(in[0])
|
||||
case map[string]any:
|
||||
rawType, ok := in[KEY_TYPE]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
strType, ok := rawType.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
urlType, err := url.Parse(strType)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
value := TryParseValueType[string](in[KEY_SCHEMA_VALUE])
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
name := TryParseValueType[string](in[KEY_ACTIVITYSTREAMS_NAME])
|
||||
if name == nil {
|
||||
return nil
|
||||
}
|
||||
return &ActivityStreamsAttachment{
|
||||
Type: urlType,
|
||||
Name: name.Value,
|
||||
Value: value.Value,
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func TryParseActivityStreamsPublished(rawIn any) *ActivityStreamsPublished {
|
||||
switch in := rawIn.(type) {
|
||||
case []any:
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
}
|
||||
return TryParseActivityStreamsPublished(in[0])
|
||||
case map[string]any:
|
||||
rawType, ok := in[KEY_TYPE]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
strType, ok := rawType.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
value := TryParseValueType[string](in)
|
||||
tv, err := time.Parse("2006-01-02T04:05:06Z", value.Value)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &ActivityStreamsPublished{
|
||||
Type: strType,
|
||||
Value: tv,
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Since I do not know if tags are consistent with the struct yet,
|
||||
// This funtion does not do anything yet and should not be used
|
||||
func TryParseActivityStreamsTag(rawIn any) *ActivityStreamsTag {
|
||||
return nil
|
||||
}
|
19
ap/util.go
Normal file
19
ap/util.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package ap
|
||||
|
||||
import "strings"
|
||||
|
||||
type InvalidFullHandleError struct {
|
||||
Raw string
|
||||
}
|
||||
|
||||
func (i InvalidFullHandleError) Error() string {
|
||||
return "Invalid full handle"
|
||||
}
|
||||
|
||||
func SplitFullHandle(full string) (string, string, error) {
|
||||
splits := strings.Split(strings.TrimPrefix(full, "@"), "@")
|
||||
if len(splits) != 2 {
|
||||
return "", "", InvalidFullHandleError{}
|
||||
}
|
||||
return splits[0], splits[1], nil
|
||||
}
|
87
ap/webfinger.go
Normal file
87
ap/webfinger.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
package ap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Data returned from a webfinger response (and also sent when asked for an account via webfinger)
|
||||
type WebfingerData struct {
|
||||
// What this webfinger data refers to. Accounts are usually `acct:username@host`
|
||||
Subject string `json:"subject"`
|
||||
// List of links related to the account
|
||||
Links []struct {
|
||||
// What type of link this is
|
||||
// - `self` refers to the activitypub object and has Type and Href set
|
||||
// - `http://webfinger.net/rel/profile-page` refers to the public webpage of the account and has Type and Href set
|
||||
// - `http://ostatus.org/schema/1.0/subscribe` provides a template for subscribing/following the account. Has Template set
|
||||
// Template will contain a `{uri}` part with which to replace idk yet
|
||||
Relation string `json:"rel"`
|
||||
// The content type of the url
|
||||
Type *string `json:"type"`
|
||||
// The url
|
||||
Href *string `json:"href"`
|
||||
// Template to use for something
|
||||
Template *string `json:"template"`
|
||||
} `json:"links"`
|
||||
}
|
||||
|
||||
var ErrAccountNotFound = errors.New("account not found")
|
||||
var ErrRemoteServerFailed = errors.New("remote server errored out")
|
||||
|
||||
// Get the webfinger data for a given full account handle (like @bob@example.com or bob@example.com)
|
||||
func GetAccountWebfinger(fullHandle string) (*WebfingerData, error) {
|
||||
// First get the handle and server domain from the full handle (and ensure it's a correctly formatted one)
|
||||
handle, server, err := SplitFullHandle(fullHandle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webfingerRequest, err := http.NewRequest(
|
||||
// Webfinger requests are GET
|
||||
"GET",
|
||||
// The webfinger url is located at <domain>/.well-known/webfinger
|
||||
// The url parameter `resource` tells the remote server what data we want, should always be an account
|
||||
// The prefix `acct:<full-handle>` (where full-handle is in the form of bob@example.com)
|
||||
// tells the remote server that we want the account data of the given account handle
|
||||
"https://"+server+"/.well-known/webfinger?resource=acct:"+handle+"@"+server,
|
||||
// No request body since it's a get request and we only need the url parameter
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Make a http client with a timeout limit of 30 seconds
|
||||
client := http.Client{Timeout: time.Second * 30}
|
||||
// Then send the request
|
||||
result, err := client.Do(webfingerRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Code 404 indicates that the account doesn't exist
|
||||
if result.StatusCode == 404 {
|
||||
return nil, ErrAccountNotFound
|
||||
} else if result.StatusCode != 200 {
|
||||
// And anything other than 200 or 404 is an error for now
|
||||
// TODO: Add information gathering here to figure out what the remote server's problem is
|
||||
return nil, ErrRemoteServerFailed
|
||||
}
|
||||
|
||||
// Get the body data from the response
|
||||
data, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Then try and parse it into webfinger data
|
||||
webfinger := WebfingerData{}
|
||||
err = json.Unmarshal(data, &webfinger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &webfinger, nil
|
||||
}
|
155
config/config.go
155
config/config.go
|
@ -1,26 +1,161 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gitlab.com/mstarongitlab/goutils/other"
|
||||
)
|
||||
|
||||
type ConfigSSL struct {
|
||||
HandleSSL bool // Whether Linstrom should handle SSL encryption itself
|
||||
HandleSSL bool `toml:"handle_ssl"` // Whether Linstrom should handle SSL encryption itself
|
||||
// If Linstrom is to handle SSL, whether it should use LetsEncrypt for certificates
|
||||
UseLetsEncrypt *bool
|
||||
UseLetsEncrypt *bool `toml:"use_lets_encrypt"`
|
||||
// Path to the certificate if Linstrom is to handle SSL while not using LetsEncrypt
|
||||
CertificateFile *string
|
||||
CertificateFile *string `toml:"certificate_file"`
|
||||
// Mail adress to use in case of using LetsEncrypt
|
||||
AdminMail *string
|
||||
AdminMail *string `toml:"admin_mail"`
|
||||
}
|
||||
|
||||
type ConfigGeneral struct {
|
||||
Domain string // The domain this server operates under
|
||||
Protocol string `toml:"protocol"` // The protocol with which to access the server publicly (http/https)
|
||||
// The subdomain under which the server lives (example: "linstrom" if the full domain is linstrom.example.com)
|
||||
Subdomain *string `toml:"subdomain"`
|
||||
// The root domain under which the server lives (example: "example.com" if the full domain is linstrom.example.com)
|
||||
Domain string `toml:"domain"`
|
||||
// The port on which the server runs on
|
||||
PrivatePort int `toml:"private_port"`
|
||||
// The port under which the public can reach the server (useful if running behind a reverse proxy)
|
||||
PublicPort *int `toml:"public_port"`
|
||||
// Url to the database to use. Can be a postgres url or the path to a sqlite file
|
||||
Database string `toml:"database"`
|
||||
// Whether the given database url is for a postgres server. If not set, assumes sqlite
|
||||
DbIsPostgres *bool `toml:"db_is_postgres"`
|
||||
}
|
||||
|
||||
type ConfigWebAuthn struct {
|
||||
DisplayName string `toml:"display_name"`
|
||||
HashingSecret string `toml:"hashing_secret"`
|
||||
}
|
||||
|
||||
type ConfigAdmin struct {
|
||||
Username string
|
||||
PasswordHash string
|
||||
// Name of the server's root admin account
|
||||
Username string `toml:"username"`
|
||||
// A one time password used to verify account access to the root admin
|
||||
// after a server has been created and before the account could be linked to a passkey
|
||||
FirstTimeSetupOTP string `toml:"first_time_setup_otp"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
General ConfigGeneral
|
||||
SSL ConfigSSL
|
||||
Admin ConfigAdmin
|
||||
General ConfigGeneral `toml:"general"`
|
||||
SSL ConfigSSL `toml:"ssl"`
|
||||
Admin ConfigAdmin `toml:"admin"`
|
||||
Webauthn ConfigWebAuthn `toml:"webauthn"`
|
||||
}
|
||||
|
||||
var GlobalConfig Config
|
||||
|
||||
var defaultConfig Config = Config{
|
||||
General: ConfigGeneral{
|
||||
Protocol: "http",
|
||||
Subdomain: nil,
|
||||
Domain: "localhost",
|
||||
PrivatePort: 8080,
|
||||
PublicPort: nil,
|
||||
Database: "db.sqlite",
|
||||
DbIsPostgres: other.IntoPointer(false),
|
||||
},
|
||||
SSL: ConfigSSL{
|
||||
HandleSSL: false,
|
||||
UseLetsEncrypt: nil,
|
||||
CertificateFile: nil,
|
||||
AdminMail: nil,
|
||||
},
|
||||
Admin: ConfigAdmin{
|
||||
Username: "server-admin",
|
||||
FirstTimeSetupOTP: "Example otp password",
|
||||
},
|
||||
Webauthn: ConfigWebAuthn{
|
||||
DisplayName: "Linstrom",
|
||||
HashingSecret: "some super secure secret that should never be changed or else password storage breaks",
|
||||
},
|
||||
}
|
||||
|
||||
func (gc *ConfigGeneral) GetFullDomain() string {
|
||||
if gc.Subdomain != nil {
|
||||
return *gc.Subdomain + gc.Domain
|
||||
}
|
||||
return gc.Domain
|
||||
}
|
||||
|
||||
func (gc *ConfigGeneral) GetFullPublicUrl() string {
|
||||
str := gc.Protocol + gc.GetFullDomain()
|
||||
if gc.PublicPort != nil {
|
||||
str += fmt.Sprint(*gc.PublicPort)
|
||||
} else {
|
||||
str += fmt.Sprint(gc.PrivatePort)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func WriteDefaultConfig(toFile string) error {
|
||||
log.Trace().Caller().Send()
|
||||
log.Info().Str("config-file", toFile).Msg("Writing default config to file")
|
||||
file, err := os.Create(toFile)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("config-file", toFile).
|
||||
Msg("Failed to create file for default config")
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, err := toml.Marshal(&defaultConfig)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to marshal default config to toml")
|
||||
return err
|
||||
}
|
||||
_, err = file.Write(data)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("config-file", toFile).Msg("Failed to write default config")
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Str("config-file", toFile).Msg("Wrote default config")
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadAndWriteToGlobal(fileName string) error {
|
||||
log.Trace().Caller().Send()
|
||||
log.Debug().Str("config-file", fileName).Msg("Attempting to read config file")
|
||||
data, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
log.Warn().
|
||||
Str("config-file", fileName).
|
||||
Err(err).
|
||||
Msg("Failed to read config file, attempting to write default config")
|
||||
err = WriteDefaultConfig(fileName)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("config-file", fileName).
|
||||
Msg("Failed to create default config file")
|
||||
return err
|
||||
}
|
||||
GlobalConfig = defaultConfig
|
||||
return nil
|
||||
}
|
||||
config := Config{}
|
||||
log.Debug().Str("config-file", fileName).Msg("Read config file, attempting to unmarshal")
|
||||
err = toml.Unmarshal(data, &config)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Bytes("config-data-raw", data).Msg("Failed to unmarshal config file")
|
||||
return err
|
||||
}
|
||||
GlobalConfig = config
|
||||
log.Info().Str("config-file", fileName).Msg("Read and applied config file")
|
||||
return nil
|
||||
}
|
||||
|
|
25
flags.go
Normal file
25
flags.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package main
|
||||
|
||||
import "flag"
|
||||
|
||||
var (
|
||||
flagPrettyPrint *bool = flag.Bool(
|
||||
"pretty",
|
||||
false,
|
||||
"If set, pretty prints logging entries which would be json objects otherwise",
|
||||
)
|
||||
flagConfigFile *string = flag.String(
|
||||
"config",
|
||||
"config.toml",
|
||||
"Location of the config file. Defaults to \"config.toml\"",
|
||||
)
|
||||
flagLogLevel *string = flag.String(
|
||||
"loglevel",
|
||||
"Info",
|
||||
"Set the logging level. Options are: Trace, Debug, Info, Warning, Error, Fatal. Capitalisation doesn't matter. Defaults to Info",
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
}
|
1
frontend
Submodule
1
frontend
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 94f7c146f67651d44c190163c8ac6312024dea90
|
41
go.mod
41
go.mod
|
@ -1,37 +1,50 @@
|
|||
module gitlab.com/mstarongitlab/linstrom
|
||||
|
||||
go 1.22.2
|
||||
go 1.23
|
||||
|
||||
toolchain go1.23.0
|
||||
|
||||
require (
|
||||
github.com/piprate/json-gold v0.5.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/BurntSushi/toml v1.4.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-webauthn/webauthn v0.11.2
|
||||
github.com/mstarongithub/passkey v0.0.0-20240817142622-de6912c8303e
|
||||
github.com/rs/zerolog v1.33.0
|
||||
gitlab.com/mstarongitlab/goutils v1.3.0
|
||||
gorm.io/driver/postgres v1.5.7
|
||||
gorm.io/gorm v1.25.10
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/datainq/xml-date-time v0.0.0-20170820214645-2292f08baa38 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.6.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/glebarez/sqlite v1.11.0 // indirect
|
||||
github.com/go-webauthn/webauthn v0.10.2 // indirect
|
||||
github.com/go-webauthn/x v0.1.9 // indirect
|
||||
github.com/go-webauthn/x v0.1.14 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/google/go-tpm v0.9.0 // indirect
|
||||
github.com/google/go-tpm v0.9.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.4.3 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/testify v1.9.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gorm.io/driver/postgres v1.5.7 // indirect
|
||||
gitlab.com/mstarongitlab/goap v0.0.0-20240826151945-33a82e544bfb // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/sys v0.23.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
|
|
82
go.sum
82
go.sum
|
@ -1,24 +1,31 @@
|
|||
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/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/datainq/xml-date-time v0.0.0-20170820214645-2292f08baa38 h1:cUoduvNB9/JFJYEVeKy2hX/R5qyg2uwnHVhXCIjhBeI=
|
||||
github.com/datainq/xml-date-time v0.0.0-20170820214645-2292f08baa38/go.mod h1:a0PnbhSGmzFMGx9KdEqfEdDHoEwTF9KLNbKLcWD8kAo=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
|
||||
github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-webauthn/webauthn v0.10.2 h1:OG7B+DyuTytrEPFmTX503K77fqs3HDK/0Iv+z8UYbq4=
|
||||
github.com/go-webauthn/webauthn v0.10.2/go.mod h1:Gd1IDsGAybuvK1NkwUTLbGmeksxuRJjVN2PE/xsPxHs=
|
||||
github.com/go-webauthn/x v0.1.9 h1:v1oeLmoaa+gPOaZqUdDentu6Rl7HkSSsmOT6gxEQHhE=
|
||||
github.com/go-webauthn/x v0.1.9/go.mod h1:pJNMlIMP1SU7cN8HNlKJpLEnFHCygLCvaLZ8a1xeoQA=
|
||||
github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc=
|
||||
github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0=
|
||||
github.com/go-webauthn/x v0.1.14 h1:1wrB8jzXAofojJPAaRxnZhRgagvLGnLjhCAwg3kTpT0=
|
||||
github.com/go-webauthn/x v0.1.14/go.mod h1:UuVvFZ8/NbOnkDz3y1NaxtUN87pmtpC1PQ+/5BBQRdc=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
|
||||
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/go-tpm v0.9.1 h1:0pGc4X//bAlmZzMKf8iz6IsDo1nYTbYJ6FZN/rg4zdM=
|
||||
github.com/google/go-tpm v0.9.1/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
|
@ -31,40 +38,55 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
|||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/piprate/json-gold v0.5.0 h1:RmGh1PYboCFcchVFuh2pbSWAZy4XJaqTMU4KQYsApbM=
|
||||
github.com/piprate/json-gold v0.5.0/go.mod h1:WZ501QQMbZZ+3pXFPhQKzNwS1+jls0oqov3uQ2WasLs=
|
||||
github.com/mstarongithub/passkey v0.0.0-20240817142622-de6912c8303e h1:BjuYFWZZd3O3RCMezWeO1CaJAOiSv2U3Pd8+nl9gDvA=
|
||||
github.com/mstarongithub/passkey v0.0.0-20240817142622-de6912c8303e/go.mod h1:vsjtQX07PZmKGSwixqXoKg6bvo3GTCA0GIwjCQ6qpHI=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
gitlab.com/mstarongitlab/goap v0.0.0-20240826151945-33a82e544bfb h1:3hzIIECbUNKm2ukwq31KjFc3+s2oixSxjW68HFcRGQM=
|
||||
gitlab.com/mstarongitlab/goap v0.0.0-20240826151945-33a82e544bfb/go.mod h1:rt9IYvJBPh1z6t+vvzifmxDtGjGlr8683tSPfa5dbXI=
|
||||
gitlab.com/mstarongitlab/goutils v1.3.0 h1:uuxPHjIU36lyJ8/z4T2xI32zOyh53Xj0Au8K12qkaJ4=
|
||||
gitlab.com/mstarongitlab/goutils v1.3.0/go.mod h1:SvqfzFxgashuZPqR9kPwQ9gFA7I1yskZjhmGmY2pAow=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
51
main.go
Normal file
51
main.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gitlab.com/mstarongitlab/linstrom/ap"
|
||||
"gitlab.com/mstarongitlab/linstrom/config"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if *flagPrettyPrint {
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||
log.Info().Msg("Pretty logging enabled")
|
||||
}
|
||||
setLogLevel()
|
||||
if err := config.ReadAndWriteToGlobal(*flagConfigFile); err != nil {
|
||||
log.Fatal().
|
||||
Err(err).
|
||||
Str("config-file", *flagConfigFile).
|
||||
Msg("Failed to read config and couldn't write default")
|
||||
}
|
||||
// "@aufricus_athudath@activitypub.academy"
|
||||
res, err := ap.GetAccountWebfinger("@aufricus_athudath@activitypub.academy")
|
||||
log.Info().
|
||||
Err(err).
|
||||
Any("webfinger", res).
|
||||
Msg("Webfinger request result for @aufricus_athudath@activitypub.academy")
|
||||
}
|
||||
|
||||
func setLogLevel() {
|
||||
log.Info().Str("new-level", *flagLogLevel).Msg("Attempting to set log level")
|
||||
switch strings.ToLower(*flagLogLevel) {
|
||||
case "trace":
|
||||
zerolog.SetGlobalLevel(zerolog.TraceLevel)
|
||||
case "debug":
|
||||
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||
case "info":
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
case "warn":
|
||||
zerolog.SetGlobalLevel(zerolog.WarnLevel)
|
||||
case "error":
|
||||
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
|
||||
case "fatal":
|
||||
zerolog.SetGlobalLevel(zerolog.FatalLevel)
|
||||
default:
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
}
|
||||
}
|
|
@ -1,36 +1,38 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"gitlab.com/mstarongitlab/linstrom/server/middlewares"
|
||||
"github.com/rs/zerolog"
|
||||
"gitlab.com/mstarongitlab/linstrom/storage"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Mount under /.well-known/webfinger
|
||||
func webfingerHandler(w http.ResponseWriter, r *http.Request) {
|
||||
logEntry, ok := r.Context().Value(middlewares.CONTEXT_KEY_LOGRUS).(*logrus.Entry)
|
||||
if !ok {
|
||||
http.Error(w, "couldn't get logging entry from context", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
logger := zerolog.Ctx(r.Context())
|
||||
store := storage.Storage{}
|
||||
|
||||
requestedResource := r.FormValue("resource")
|
||||
if requestedResource == "" {
|
||||
http.Error(w, "bad request. Include \"resource\" parameter", http.StatusBadRequest)
|
||||
logEntry.Infoln("No resource parameter. Cancelling")
|
||||
logger.Debug().Msg("Resource parameter missing. Cancelling")
|
||||
return
|
||||
}
|
||||
accName := strings.TrimPrefix(requestedResource, "acc:")
|
||||
acc, err := store.FindLocalAccount(accName)
|
||||
acc, err := store.FindAccountByFullHandle(accName)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
logEntry.WithError(err).Warningln("couldn't find account")
|
||||
return
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
http.Error(w, "account not found", http.StatusNotFound)
|
||||
logger.Debug().Str("account-name", accName).Msg("Account not found")
|
||||
} else {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
logger.Error().Err(err).Msg("Error while searching for account")
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Fprint(w, acc)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
package middlewares
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const CONTEXT_KEY_LOGRUS = ContextKey("logrus")
|
||||
|
@ -12,9 +11,7 @@ const CONTEXT_KEY_LOGRUS = ContextKey("logrus")
|
|||
// Inject a logrus entry into the context that has the url path already set
|
||||
func InjectLogrusMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
reqContext := r.Context()
|
||||
entry := logrus.WithField("url-path", r.URL.Path)
|
||||
newContext := context.WithValue(reqContext, CONTEXT_KEY_LOGRUS, entry)
|
||||
newContext := log.With().Str("url-path", r.URL.RawPath).Logger().WithContext(r.Context())
|
||||
next.ServeHTTP(w, r.WithContext(newContext))
|
||||
})
|
||||
}
|
||||
|
|
7
storage/errors.go
Normal file
7
storage/errors.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package storage
|
||||
|
||||
type NotImplementedError struct{}
|
||||
|
||||
func (n NotImplementedError) Error() string {
|
||||
return "Not implemented yet"
|
||||
}
|
|
@ -7,9 +7,9 @@ import (
|
|||
)
|
||||
|
||||
type MediaFile struct {
|
||||
ID string `gorm:"primarykey"` // The unique ID of this media file
|
||||
CreatedAt time.Time // When this entry was created
|
||||
UpdatedAt time.Time // When this entry was last updated
|
||||
ID string `gorm:"primarykey"` // The unique ID of this media file
|
||||
CreatedAt time.Time // When this entry was created
|
||||
UpdatedAt time.Time // When this entry was last updated
|
||||
// When this entry was deleted (for soft deletions)
|
||||
// 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
|
||||
|
@ -25,12 +25,3 @@ type MediaFile struct {
|
|||
// Caching user and server icons locally however should reduce burden on remote servers by quite a bit though
|
||||
LocallyCached bool
|
||||
}
|
||||
|
||||
// Placeholder media file. Acts as placeholder for media file fields that have not been initialised yet but need a value
|
||||
var placeholderMediaFile = &MediaFile{
|
||||
ID: "placeholder",
|
||||
Remote: false,
|
||||
Link: "placeholder", // TODO: Replace this with a file path to a staticly included image
|
||||
Type: "image/png",
|
||||
LocallyCached: true,
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@ import (
|
|||
)
|
||||
|
||||
type Note struct {
|
||||
ID string `gorm:"primarykey"` // Make ID a string (uuid) for other implementations
|
||||
CreatedAt time.Time // When this entry was created
|
||||
UpdatedAt time.Time // When this entry was last updated
|
||||
ID string `gorm:"primarykey"` // Make ID a string (uuid) for other implementations
|
||||
CreatedAt time.Time // When this entry was created
|
||||
UpdatedAt time.Time // When this entry was last updated
|
||||
// When this entry was deleted (for soft deletions)
|
||||
// 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
|
||||
|
@ -29,19 +29,3 @@ type Note struct {
|
|||
OriginServer string // Url of the origin server. Also the primary key for those
|
||||
Tags []string `gorm:"serializer:json"` // Hashtags
|
||||
}
|
||||
|
||||
var placeholderNote = &Note{
|
||||
ID: "placeholder",
|
||||
Creator: "placeholder",
|
||||
Remote: false,
|
||||
RawContent: "placeholder",
|
||||
ContentWarning: nil,
|
||||
Attachments: []string{},
|
||||
Emotes: []string{},
|
||||
RepliesTo: nil,
|
||||
Quotes: nil,
|
||||
Target: NOTE_TARGET_HOME,
|
||||
Pings: []string{},
|
||||
OriginServer: "placeholder",
|
||||
Tags: []string{},
|
||||
}
|
||||
|
|
45
storage/passkeySessions.go
Normal file
45
storage/passkeySessions.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type PasskeySession struct {
|
||||
ID string `gorm:"primarykey"`
|
||||
Data webauthn.SessionData `gorm:"serializer:json"`
|
||||
}
|
||||
|
||||
// ---- Section SessionStore
|
||||
|
||||
func (s *Storage) GenSessionID() (string, error) {
|
||||
x := uuid.NewString()
|
||||
log.Debug().Str("session-id", x).Msg("Generated new passkey session id")
|
||||
return x, nil
|
||||
}
|
||||
|
||||
func (s *Storage) GetSession(sessionId string) (*webauthn.SessionData, bool) {
|
||||
log.Debug().Str("id", sessionId).Msg("Looking for passkey session")
|
||||
session := PasskeySession{}
|
||||
res := s.db.Where("id = ?", sessionId).First(&session)
|
||||
if res.Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
log.Debug().Str("id", sessionId).Any("webauthn-data", &session).Msg("Found passkey session")
|
||||
return &session.Data, true
|
||||
}
|
||||
|
||||
func (s *Storage) SaveSession(token string, data *webauthn.SessionData) {
|
||||
log.Debug().Str("id", token).Any("webauthn-data", data).Msg("Saving passkey session")
|
||||
session := PasskeySession{
|
||||
ID: token,
|
||||
Data: *data,
|
||||
}
|
||||
s.db.Save(&session)
|
||||
}
|
||||
|
||||
func (s *Storage) DeleteSession(token string) {
|
||||
log.Debug().Str("id", token).Msg("Deleting passkey session (if one exists)")
|
||||
s.db.Delete(&PasskeySession{ID: token})
|
||||
}
|
|
@ -7,9 +7,9 @@ import (
|
|||
)
|
||||
|
||||
type RemoteServer struct {
|
||||
ID string `gorm:"primarykey"` // ID is also server url
|
||||
CreatedAt time.Time // When this entry was created
|
||||
UpdatedAt time.Time // When this entry was last updated
|
||||
ID string `gorm:"primarykey"` // ID is also server url
|
||||
CreatedAt time.Time // When this entry was created
|
||||
UpdatedAt time.Time // When this entry was last updated
|
||||
// When this entry was deleted (for soft deletions)
|
||||
// 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
|
||||
|
@ -19,11 +19,3 @@ type RemoteServer struct {
|
|||
Icon string // ID of a media file
|
||||
IsSelf bool // Whether this server is yours truly
|
||||
}
|
||||
|
||||
var placeholderServer = &RemoteServer{
|
||||
ID: "placeholder",
|
||||
ServerType: REMOTE_SERVER_LINSTROM,
|
||||
Name: "placeholder",
|
||||
Icon: "placeholder",
|
||||
IsSelf: false,
|
||||
}
|
||||
|
|
5
storage/remoteUser.go
Normal file
5
storage/remoteUser.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
package storage
|
||||
|
||||
func (s *Storage) NewRemoteUser(fullHandle string) (*Account, error) {
|
||||
return nil, nil
|
||||
}
|
8
storage/roles.go
Normal file
8
storage/roles.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package storage
|
||||
|
||||
type Role struct {
|
||||
// Name of the role
|
||||
Name string
|
||||
// If set, counts as all permissions being set and all restrictions being disabled
|
||||
FullAdmin bool
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
|
@ -35,34 +33,16 @@ func storageFromEmptyDb(db *gorm.DB) (*Storage, error) {
|
|||
// AutoMigrate ensures the db is in a state where all the structs given here
|
||||
// have their own tables and relations setup. It also updates tables if necessary
|
||||
db.AutoMigrate(
|
||||
placeholderMediaFile,
|
||||
placeholderUser(),
|
||||
placeholderNote,
|
||||
placeholderServer,
|
||||
MediaFile{},
|
||||
Account{},
|
||||
RemoteServer{},
|
||||
Note{},
|
||||
Role{},
|
||||
PasskeySession{},
|
||||
)
|
||||
// Afterwards add the placeholder entries for each table.
|
||||
// FirstOrCreate either creates a new entry or retrieves the first matching one
|
||||
// We only care about the creation if there is none yet, so no need to carry the result over
|
||||
if res := db.FirstOrCreate(placeholderMediaFile); res.Error != nil {
|
||||
return nil, fmt.Errorf("failed to add placeholder media file: %w", res.Error)
|
||||
}
|
||||
if res := db.FirstOrCreate(placeholderUser()); res.Error != nil {
|
||||
return nil, fmt.Errorf("failed to add placeholder media file: %w", res.Error)
|
||||
}
|
||||
if res := db.FirstOrCreate(placeholderNote); res.Error != nil {
|
||||
return nil, fmt.Errorf("failed to add placeholder media file: %w", res.Error)
|
||||
}
|
||||
if res := db.FirstOrCreate(placeholderServer); res.Error != nil {
|
||||
return nil, fmt.Errorf("failed to add placeholder media file: %w", res.Error)
|
||||
}
|
||||
|
||||
// And finally, build the actual storage struct
|
||||
return &Storage{
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO: Placeholder. Update to proper implementation later. Including signature
|
||||
func (s *Storage) FindLocalAccount(handle string) (string, error) {
|
||||
return handle, nil
|
||||
}
|
||||
|
|
278
storage/user.go
278
storage/user.go
|
@ -1,21 +1,30 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
"github.com/google/uuid"
|
||||
"github.com/mstarongithub/passkey"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gitlab.com/mstarongitlab/linstrom/ap"
|
||||
"gitlab.com/mstarongitlab/linstrom/config"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Database representation of a user account
|
||||
// This can be a bot, remote or not
|
||||
// If remote, this is used for caching the account
|
||||
type User struct {
|
||||
ID string `gorm:"primarykey"` // ID is a uuid for this account
|
||||
Handle string // Handle is the full handle, eg @max@example.com
|
||||
CreatedAt time.Time // When this entry was created
|
||||
UpdatedAt time.Time // When this account was last updated. Will also be used for refreshing remote accounts
|
||||
type Account struct {
|
||||
ID string `gorm:"primarykey"` // ID is a uuid for this account
|
||||
// Handle of the user (eg "max" if the full username is @max@example.com)
|
||||
// Assume unchangable (once set by a user) to be kind to other implementations
|
||||
// Would be an easy avenue to fuck with them though
|
||||
Handle string
|
||||
CreatedAt time.Time // When this entry was created. Automatically set by gorm
|
||||
// When this account was last updated. Will also be used for refreshing remote accounts. Automatically set by gorm
|
||||
UpdatedAt time.Time
|
||||
// When this entry was deleted (for soft deletions)
|
||||
// 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
|
||||
|
@ -39,57 +48,230 @@ type User struct {
|
|||
RestrictedFollow bool
|
||||
// List of things the owner identifies as
|
||||
// Example [cat human robot] means that the owner probably identifies as
|
||||
// a cyborg-catgirl/boy/human
|
||||
IdentifiesAs []Being
|
||||
// a cyborg-catgirl/boy/human or a cathuman shaped robot, refer to Gender for pronouns
|
||||
IdentifiesAs []Being `gorm:"serializer:json"`
|
||||
// List of pronouns the owner identifies with
|
||||
// An unordered list since the owner can freely set it
|
||||
// Examples: [she her], [it they its them]
|
||||
Gender []string
|
||||
Gender []string `gorm:"serializer:json"`
|
||||
// The roles assocciated with an account
|
||||
Roles []string `gorm:"serializer:json"`
|
||||
|
||||
// --- And internal account stuff ---
|
||||
// Still public fields since they wouldn't be able to be stored in the db otherwise
|
||||
|
||||
PasswordHash []byte // Hash of the user's password
|
||||
TotpToken []byte // Token for totp verification
|
||||
// All the registered passkeys, name of passkey to credentials
|
||||
// Could this be exported to another table? Yes
|
||||
// Would it make sense? Probably not
|
||||
// Will just take the performance hit of json conversion
|
||||
// Access should be rare enough anyway
|
||||
Passkeys map[string]webauthn.Credential `gorm:"serializer:json"`
|
||||
PrivateKeyPem *string // The private key of the account. Nil if remote user
|
||||
PrivateKeyPem *string // The private key of the account. Nil if remote user
|
||||
WebAuthnId []byte // The unique and random ID of this account used for passkey authentication
|
||||
// Whether the account got verified and is allowed to be active
|
||||
// For local accounts being active means being allowed to login and perform interactions
|
||||
// For remote users, if an account is not verified, any interactions it sends are discarded
|
||||
Verified bool
|
||||
PasskeyCredentials []webauthn.Credential `gorm:"serializer:json"` // Webauthn credentials data
|
||||
// Has a RemoteAccountLinks included if remote user
|
||||
RemoteLinks *RemoteAccountLinks
|
||||
}
|
||||
|
||||
func NewEmptyUser() *User {
|
||||
return &User{
|
||||
ID: uuid.NewString(),
|
||||
Handle: "placeholder",
|
||||
Remote: false,
|
||||
Server: "placeholder",
|
||||
DisplayName: "placeholder",
|
||||
CustomFields: []uint{},
|
||||
Description: "placeholder",
|
||||
Tags: []string{},
|
||||
IsBot: true,
|
||||
Follows: []string{},
|
||||
Followers: []string{},
|
||||
Icon: "placeholder",
|
||||
Background: "placeholder",
|
||||
Banner: "placeholder",
|
||||
Indexable: false,
|
||||
PublicKeyPem: nil,
|
||||
RestrictedFollow: false,
|
||||
IdentifiesAs: []Being{BEING_ROBOT},
|
||||
Gender: []string{"it", "its"},
|
||||
PasswordHash: []byte("placeholder"),
|
||||
TotpToken: []byte("placeholder"),
|
||||
Passkeys: map[string]webauthn.Credential{},
|
||||
PrivateKeyPem: nil,
|
||||
// Contains static and cached info about a remote account, mostly links
|
||||
type RemoteAccountLinks struct {
|
||||
// ---- Section: gorm
|
||||
// Sets this struct up as a value that an Account may have
|
||||
gorm.Model
|
||||
AccountID string
|
||||
|
||||
// Just about every link here is optional to accomodate for servers with only minimal accounts
|
||||
// Minimal being handle, ap link and inbox
|
||||
ApLink string
|
||||
ViewLink *string
|
||||
FollowersLink *string
|
||||
FollowingLink *string
|
||||
InboxLink string
|
||||
OutboxLink *string
|
||||
FeaturedLink *string
|
||||
FeaturedTagsLink *string
|
||||
}
|
||||
|
||||
// Find an account in the db using a given full handle (@max@example.com)
|
||||
// Returns an account and nil if an account is found, otherwise nil and the error
|
||||
func (s *Storage) FindAccountByFullHandle(handle string) (*Account, error) {
|
||||
log.Trace().Caller().Send()
|
||||
log.Debug().Str("account-handle", handle).Msg("Looking for account by handle")
|
||||
name, server, err := ap.SplitFullHandle(handle)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("account-handle", handle).Msg("Failed to split up account handle")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
acc := Account{}
|
||||
res := s.db.Where("name = ?", name).Where("server = ?", server).First(&acc)
|
||||
if res.Error != nil {
|
||||
if errors.Is(res.Error, gorm.ErrRecordNotFound) {
|
||||
log.Info().Str("account-handle", handle).Msg("Account with handle not found")
|
||||
} else {
|
||||
log.Error().Err(err).Str("account-handle", handle).Msg("Failed to get account with handle")
|
||||
}
|
||||
return nil, res.Error
|
||||
}
|
||||
log.Info().Str("account-handle", handle).Msg("Found account")
|
||||
return &acc, nil
|
||||
}
|
||||
|
||||
func placeholderUser() *User {
|
||||
tmp := NewEmptyUser()
|
||||
tmp.ID = "placeholder"
|
||||
return tmp
|
||||
// Find an account given a specific ID
|
||||
func (s *Storage) FindAccountById(id string) (*Account, error) {
|
||||
log.Trace().Caller().Send()
|
||||
log.Debug().Str("account-id", id).Msg("Looking for account by id")
|
||||
acc := Account{}
|
||||
res := s.db.First(&acc, id)
|
||||
if res.Error != nil {
|
||||
if errors.Is(res.Error, gorm.ErrRecordNotFound) {
|
||||
log.Warn().Str("account-id", id).Msg("Account not found")
|
||||
} else {
|
||||
log.Error().Err(res.Error).Str("account-id", id).Msg("Failed to look for account")
|
||||
}
|
||||
return nil, res.Error
|
||||
}
|
||||
log.Info().Str("account-id", id).Msg("Found account")
|
||||
return &acc, nil
|
||||
}
|
||||
|
||||
func (s *Storage) NewEmptyAccount() (*Account, error) {
|
||||
log.Trace().Caller().Send()
|
||||
log.Debug().Msg("Creating new empty account")
|
||||
acc := Account{}
|
||||
// Generate the 64 bit id for passkey and webauthn stuff
|
||||
data := make([]byte, 64)
|
||||
c, err := rand.Read(data)
|
||||
for err != nil || c != len(data) || c < 64 {
|
||||
data = make([]byte, 64)
|
||||
c, err = rand.Read(data)
|
||||
}
|
||||
acc.WebAuthnId = data
|
||||
acc.Followers = []string{}
|
||||
acc.Tags = []string{}
|
||||
acc.Follows = []string{}
|
||||
acc.Gender = []string{}
|
||||
acc.CustomFields = []uint{}
|
||||
acc.IdentifiesAs = []Being{}
|
||||
acc.PasskeyCredentials = []webauthn.Credential{}
|
||||
res := s.db.Save(acc)
|
||||
if res.Error != nil {
|
||||
log.Error().Err(res.Error).Msg("Failed to safe new account")
|
||||
return nil, res.Error
|
||||
}
|
||||
log.Info().Str("account-id", acc.ID).Msg("Created new account")
|
||||
return &acc, nil
|
||||
}
|
||||
|
||||
func (s *Storage) NewLocalAccount(handle string) (*Account, error) {
|
||||
log.Trace().Caller().Send()
|
||||
log.Debug().Str("account-handle", handle).Msg("Creating new local account")
|
||||
acc, err := s.NewEmptyAccount()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to create empty account for use")
|
||||
return nil, err
|
||||
}
|
||||
acc.Handle = handle
|
||||
acc.Server = config.GlobalConfig.General.GetFullDomain()
|
||||
acc.Remote = false
|
||||
acc.DisplayName = handle
|
||||
|
||||
log.Debug().
|
||||
Str("account-handle", handle).
|
||||
Str("account-id", acc.ID).
|
||||
Msg("Saving new local account")
|
||||
res := s.db.Save(acc)
|
||||
if res.Error != nil {
|
||||
log.Error().Err(res.Error).Any("account-full", acc).Msg("Failed to save local account")
|
||||
return nil, res.Error
|
||||
}
|
||||
log.Info().
|
||||
Str("account-handle", handle).
|
||||
Str("account-id", acc.ID).
|
||||
Msg("Created new local account")
|
||||
return acc, nil
|
||||
}
|
||||
|
||||
// ---- Section WebAuthn.User
|
||||
// Implements the webauthn.User interface for interaction with passkeys
|
||||
|
||||
func (a *Account) WebAuthnID() []byte {
|
||||
log.Trace().Caller().Send()
|
||||
return a.WebAuthnId
|
||||
}
|
||||
|
||||
func (u *Account) WebAuthnName() string {
|
||||
log.Trace().Caller().Send()
|
||||
return u.Handle
|
||||
}
|
||||
|
||||
func (u *Account) WebAuthnDisplayName() string {
|
||||
log.Trace().Caller().Send()
|
||||
return u.DisplayName
|
||||
}
|
||||
|
||||
func (u *Account) WebAuthnCredentials() []webauthn.Credential {
|
||||
log.Trace().Caller().Send()
|
||||
return u.PasskeyCredentials
|
||||
}
|
||||
|
||||
func (u *Account) WebAuthnIcon() string {
|
||||
log.Trace().Caller().Send()
|
||||
return ""
|
||||
}
|
||||
|
||||
// ---- Section passkey.User
|
||||
// Implements the passkey.User interface
|
||||
|
||||
func (u *Account) PutCredential(new webauthn.Credential) {
|
||||
log.Trace().Caller().Send()
|
||||
u.PasskeyCredentials = append(u.PasskeyCredentials, new)
|
||||
}
|
||||
|
||||
// Section passkey.UserStore
|
||||
// Implements the passkey.UserStore interface
|
||||
|
||||
func (s *Storage) GetOrCreateUser(userID string) passkey.User {
|
||||
log.Trace().Caller().Send()
|
||||
log.Debug().
|
||||
Str("account-handle", userID).
|
||||
Msg("Looking for or creating account for passkey stuff")
|
||||
acc := &Account{}
|
||||
res := s.db.Where(Account{Handle: userID, Server: config.GlobalConfig.General.GetFullDomain()}).
|
||||
First(acc)
|
||||
if errors.Is(res.Error, gorm.ErrRecordNotFound) {
|
||||
log.Debug().Str("account-handle", userID)
|
||||
var err error
|
||||
acc, err = s.NewLocalAccount(userID)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("account-handle", userID).
|
||||
Msg("Failed to create new account for webauthn request")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
func (s *Storage) GetUserByWebAuthnId(id []byte) passkey.User {
|
||||
log.Trace().Caller().Send()
|
||||
log.Debug().Bytes("webauthn-id", id).Msg("Looking for account with webauthn id")
|
||||
acc := Account{}
|
||||
res := s.db.Where(Account{WebAuthnId: id}).First(&acc)
|
||||
if res.Error != nil {
|
||||
log.Error().
|
||||
Err(res.Error).
|
||||
Bytes("webauthn-id", id).
|
||||
Msg("Failed to find user with webauthn ID")
|
||||
return nil
|
||||
}
|
||||
log.Info().Msg("Found account with given webauthn id")
|
||||
return &acc
|
||||
}
|
||||
|
||||
func (s *Storage) SaveUser(rawUser passkey.User) {
|
||||
log.Trace().Caller().Send()
|
||||
user, ok := rawUser.(*Account)
|
||||
if !ok {
|
||||
log.Error().Any("raw-user", rawUser).Msg("Failed to cast raw user to db account")
|
||||
}
|
||||
s.db.Save(user)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// What kind of being a user identifies as
|
||||
type Being string
|
||||
|
||||
|
@ -16,16 +11,3 @@ const (
|
|||
BEING_ROBOT = Being("robot")
|
||||
BEING_DOLL = Being("doll")
|
||||
)
|
||||
|
||||
func (r *Being) Value() (driver.Value, error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *Being) Scan(raw any) error {
|
||||
if v, ok := raw.(string); ok {
|
||||
*r = Being(v)
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("value not a string")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue