This commit is contained in:
me 2024-12-17 10:47:00 +02:00
parent f6f0568594
commit 6b43b7247d
10 changed files with 68 additions and 27 deletions

2
.gitignore vendored
View file

@ -4,3 +4,5 @@ templates/
website/ website/
user-data/ user-data/
out/ out/
notes/
fedi.log

View file

@ -42,12 +42,14 @@ data NoteEntry
, name :: Maybe String , name :: Maybe String
, url :: Maybe Url , url :: Maybe Url
} }
deriving Show
data FollowerEntry data FollowerEntry
= FollowerEntry = FollowerEntry
{ followId :: T.Text { followId :: T.Text
, actorId :: T.Text , actorId :: T.Text
} }
deriving Show
data Follower data Follower
= Follower = Follower
@ -55,6 +57,7 @@ data Follower
, followId :: T.Text , followId :: T.Text
, actorId :: T.Text , actorId :: T.Text
} }
deriving Show
----------------------- -----------------------

View file

@ -16,6 +16,7 @@ import Network.Wai.Middleware.Routed qualified as Wai
import Routes import Routes
import System.Environment (getArgs, lookupEnv) import System.Environment (getArgs, lookupEnv)
import Web.Twain qualified as Twain import Web.Twain qualified as Twain
import Control.Logger.Simple qualified as Log
data Command data Command
= Serve = Serve
@ -63,15 +64,18 @@ insertNoteFromFile file = do
print note print note
serve :: IO () serve :: IO ()
serve = do serve = Log.withGlobalLogging (Log.LogConfig (Just "fedi.log") True) do
logLevel <- maybe Log.LogDebug read <$> lookupEnv "FEDI_LOG_LEVEL"
Log.setLogLevel logLevel
auth <- fmap (T.splitOn "," . T.pack) <$> lookupEnv "FEDI_AUTH" auth <- fmap (T.splitOn "," . T.pack) <$> lookupEnv "FEDI_AUTH"
authMiddleware <- authMiddleware <-
case auth of case auth of
Nothing -> do Nothing -> do
putStrLn "Starting server with authentication disabled." Log.logInfo "Starting server with authentication disabled."
pure id pure id
Just [user, pass] -> do Just [user, pass] -> do
putStrLn "Starting server with authentication enabled," Log.logInfo "Starting server with authentication enabled,"
let let
username = secureMemFromByteString $ T.encodeUtf8 user username = secureMemFromByteString $ T.encodeUtf8 user
password = secureMemFromByteString $ T.encodeUtf8 pass password = secureMemFromByteString $ T.encodeUtf8 pass
@ -87,7 +91,7 @@ serve = do
fediPort <- maybe 3001 read <$> lookupEnv "FEDI_PORT" fediPort <- maybe 3001 read <$> lookupEnv "FEDI_PORT"
conn <- maybe "/tmp/fediserve_sqlite_db.sqlite" fromString <$> lookupEnv "FEDI_CONN_STRING" conn <- maybe "/tmp/fediserve_sqlite_db.sqlite" fromString <$> lookupEnv "FEDI_CONN_STRING"
putStrLn $ "and with connection string " <> show (Sqlite.unConnectionString conn) <> "." Log.logInfo $ "and with connection string " <> Sqlite.unConnectionString conn <> "."
runServer fediPort authMiddleware =<< mkFediApp conn runServer fediPort authMiddleware =<< mkFediApp conn
@ -101,15 +105,16 @@ usageError =
, " - FEDI_DETAILS=<FILE>" , " - FEDI_DETAILS=<FILE>"
, " - FEDI_CONN_STRING=<SQLITE_CONN_STR>" , " - FEDI_CONN_STRING=<SQLITE_CONN_STR>"
, " - FEDI_AUTH=<user>,<password>" , " - FEDI_AUTH=<user>,<password>"
, " - FEDI_LOG=[ LogTrace | LogDebug | LogInfo | LogNote | LogWarn | LogError ]"
] ]
-- | Run server at at specific port. -- | Run server at at specific port.
runServer :: Port -> Twain.Middleware -> Twain.Application -> IO () runServer :: Port -> Twain.Middleware -> Twain.Application -> IO ()
runServer port authMiddleware app = do runServer port authMiddleware app = do
putStrLn $ Log.logInfo $
unwords T.unwords
[ "Running fedi at" [ "Running fedi at"
, "http://localhost:" <> show port , "http://localhost:" <> T.pack (show port)
, "(ctrl-c to quit)" , "(ctrl-c to quit)"
] ]
auth <- Wai.routedMiddleware matchAdmin <$> pure authMiddleware auth <- Wai.routedMiddleware matchAdmin <$> pure authMiddleware

View file

@ -11,6 +11,7 @@ import System.IO.Unsafe (unsafePerformIO)
import Web.Twain qualified as Twain import Web.Twain qualified as Twain
import Data.Text qualified as T import Data.Text qualified as T
import Control.Concurrent.Async qualified as Async import Control.Concurrent.Async qualified as Async
import Control.Logger.Simple qualified as Log
routes :: DB -> FilePath -> [Twain.Middleware] routes :: DB -> FilePath -> [Twain.Middleware]
routes db detailsFile = routes db detailsFile =
@ -48,6 +49,7 @@ routes db detailsFile =
, -- Match inbox , -- Match inbox
Twain.post (Fedi.matchInbox $ unsafePerformIO $ fetchUserDetails detailsFile) do Twain.post (Fedi.matchInbox $ unsafePerformIO $ fetchUserDetails detailsFile) do
Log.logTrace "Inbox"
Fedi.handleInbox (handleInbox db detailsFile) Fedi.handleInbox (handleInbox db detailsFile)
, -- Match Create object , -- Match Create object
@ -128,6 +130,7 @@ noteToCreate note = Fedi.makeCreateNote note
handleInbox :: DB -> FilePath -> Fedi.AnyActivity -> Twain.ResponderM Twain.Response handleInbox :: DB -> FilePath -> Fedi.AnyActivity -> Twain.ResponderM Twain.Response
handleInbox db detailsFile activity = do handleInbox db detailsFile activity = do
details <- liftIO $ fetchUserDetails detailsFile details <- liftIO $ fetchUserDetails detailsFile
Log.logDebug (Fedi.pShow activity)
case activity of case activity of
Fedi.ActivityFollow follow -> do Fedi.ActivityFollow follow -> do
let let
@ -153,11 +156,12 @@ handleInbox db detailsFile activity = do
follow follow
(Fedi.actorUrl details <> "/accepts/follows/" <> show insertId) (Fedi.actorUrl details <> "/accepts/follows/" <> show insertId)
) )
print result Log.logDebug (Fedi.pShow result)
pure $ Fedi.jsonLD "{}" pure $ Twain.text ""
) )
liftIO do liftIO do
insertFollower db followerEntry callback insertFollower db followerEntry callback
<* Log.logInfo ("New follower: " <> Fedi.pShow followerEntry)
else Twain.next else Twain.next
Nothing -> Nothing ->
Twain.next Twain.next
@ -182,19 +186,21 @@ handleInbox db detailsFile activity = do
{ actorId = fromString actor.unwrap { actorId = fromString actor.unwrap
, followId = fromString id''.unwrap , followId = fromString id''.unwrap
} }
print ("deleted follower: " <> show deletedId) Log.logInfo ("deleted follower: " <> Fedi.pShow deletedId)
pure $ Fedi.jsonLD "{}" pure $ Twain.text ""
else Twain.next else Twain.next
Nothing -> Nothing ->
Twain.next Twain.next
_ -> do _ -> do
liftIO (print activity) Log.logError $ "Unsupported activity: " <> Fedi.pShow activity
Twain.next Twain.next
sendFollowers :: Fedi.UserDetails -> DB -> Fedi.AnyActivity -> IO () sendFollowers :: Fedi.UserDetails -> DB -> Fedi.AnyActivity -> IO ()
sendFollowers details db message = do sendFollowers details db message = do
Log.logDebug $ "Sending to followers: " <> Fedi.pShow message
followers <- db.getFollowers followers <- db.getFollowers
Fedi.for_ followers \follower -> do Fedi.for_ followers \follower -> do
Async.async $ do Async.async $ do
Log.logDebug $ "Sending to follower: " <> Fedi.pShow follower.actorId
bs <- Fedi.sendPost details (T.unpack follower.actorId <> "/inbox") message bs <- Fedi.sendPost details (T.unpack follower.actorId <> "/inbox") message
print (follower.actorId, bs) Log.logDebug $ "Sent to follower: " <> Fedi.pShow (follower.actorId, bs)

View file

@ -64,6 +64,8 @@ library
, raw-strings-qq , raw-strings-qq
, case-insensitive , case-insensitive
, http-types , http-types
, simple-logger
, pretty-simple
hs-source-dirs: src hs-source-dirs: src
default-language: GHC2021 default-language: GHC2021
@ -102,6 +104,8 @@ executable fedi
, raw-strings-qq , raw-strings-qq
, securemem , securemem
, lucid2 , lucid2
, simple-logger
, pretty-simple
hs-source-dirs: app hs-source-dirs: app
default-language: GHC2021 default-language: GHC2021

View file

@ -12,14 +12,16 @@ import Fedi.Helpers
import Data.ByteString.Base64 qualified as Base64 import Data.ByteString.Base64 qualified as Base64
import Data.Base64.Types qualified as Base64 import Data.Base64.Types qualified as Base64
import Data.Text qualified as T import Data.Text qualified as T
import Control.Logger.Simple qualified as Log
import Control.Monad.IO.Class
-- import Debug.Trace verifyPub :: MonadIO m => MonadThrow m => ByteString -> ByteString -> ByteString -> m Bool
verifyPub :: MonadThrow m => ByteString -> ByteString -> ByteString -> m Bool
verifyPub pubkeypem sig message = do verifyPub pubkeypem sig message = do
-- traceShowM ("pubkeypem", pubkeypem) Log.logDebug $ "Verifying signature: " <> pShow
-- traceShowM ("sig", sig) [ ("pubkeypem", pubkeypem)
-- traceShowM ("mssage", message) , ("sig", sig)
, ("message", message)
]
pubkey <- pubkey <-
case Crypto.readPubKeyFileFromMemory pubkeypem of case Crypto.readPubKeyFileFromMemory pubkeypem of

View file

@ -17,6 +17,8 @@ import Control.Monad as Export
import Data.Functor as Export import Data.Functor as Export
import Data.Function as Export import Data.Function as Export
import Control.Monad.Catch as Export (throwM, Exception, MonadThrow) import Control.Monad.Catch as Export (throwM, Exception, MonadThrow)
import Text.Pretty.Simple qualified as PS
import Data.Text.Lazy qualified as TL
data Error data Error
= Error String = Error String
@ -24,3 +26,6 @@ data Error
throw :: MonadThrow m => String -> m a throw :: MonadThrow m => String -> m a
throw = throwM . Error throw = throwM . Error
pShow :: Show a => a -> Text
pShow = TL.toStrict . PS.pShow

View file

@ -11,6 +11,7 @@ import Network.HTTP.Req qualified as Req
import Data.ByteString.Lazy qualified as BSL import Data.ByteString.Lazy qualified as BSL
import Text.URI qualified as URI import Text.URI qualified as URI
import Data.Text qualified as T import Data.Text qualified as T
import Control.Logger.Simple qualified as Log
sendPost sendPost
:: A.ToJSON input :: A.ToJSON input
@ -22,10 +23,12 @@ sendPost details url payload = do
uri <- URI.mkURI $ fromString url uri <- URI.mkURI $ fromString url
let encoded = BSL.toStrict $ A.encode payload let encoded = BSL.toStrict $ A.encode payload
httpSignature <- makeHttpSignature details uri encoded httpSignature <- makeHttpSignature details uri encoded
Log.logDebug $ "http signature: " <> pShow httpSignature
Log.logDebug $ "http signature headers: " <> pShow (makeSigHeaders httpSignature)
(url', scheme) <- maybe (throw "couldn't parse uri") pure (Req.useHttpsURI uri) (url', scheme) <- maybe (throw "couldn't parse uri") pure (Req.useHttpsURI uri)
Req.runReq Req.defaultHttpConfig do Req.runReq Req.defaultHttpConfig do
r <- response <-
Req.req Req.reqCb
Req.POST Req.POST
url' url'
(Req.ReqBodyBs encoded) (Req.ReqBodyBs encoded)
@ -33,7 +36,12 @@ sendPost details url payload = do
( scheme ( scheme
<> sigHeaders httpSignature <> sigHeaders httpSignature
) )
pure $ Req.responseBody r (\request -> do
Log.logDebug $ "Sending POST request: " <> pShow request
pure request
)
Log.logInfo $ "Response: " <> pShow response
pure $ Req.responseBody response
makeHttpSignature :: UserDetails -> URI.URI -> ByteString -> IO HttpSignature makeHttpSignature :: UserDetails -> URI.URI -> ByteString -> IO HttpSignature
makeHttpSignature details uri encoded = do makeHttpSignature details uri encoded = do
@ -71,7 +79,7 @@ sendGet url = do
Req.runReq Req.defaultHttpConfig do Req.runReq Req.defaultHttpConfig do
r <- r <-
Req.req Req.reqCb
Req.GET Req.GET
url' url'
Req.NoReqBody Req.NoReqBody
@ -79,4 +87,8 @@ sendGet url = do
( scheme ( scheme
<> Req.header "ContentType" "application/activity+json" <> Req.header "ContentType" "application/activity+json"
) )
(\request -> do
Log.logDebug $ "Sending GET request: " <> pShow request
pure request
)
pure $ Req.responseBody r pure $ Req.responseBody r

View file

@ -2,12 +2,13 @@ module Fedi.Routes.Inbox where
import Prelude hiding (error) import Prelude hiding (error)
import Fedi.Types import Fedi.Types
import Fedi.Helpers
import Fedi.UserDetails import Fedi.UserDetails
import Web.Twain qualified as Twain import Web.Twain qualified as Twain
import Web.Twain.Types qualified as Twain import Web.Twain.Types qualified as Twain
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Catch (catch, displayException, SomeException) import Control.Monad.Catch (catch, displayException, SomeException)
import Fedi.Signature.Check import Fedi.Signature.Check
import Control.Logger.Simple qualified as Log
-- * Inbox -- * Inbox
@ -23,7 +24,8 @@ handleInbox handle = do
response <- handle activity response <- handle activity
Twain.send response Twain.send response
handler `catch` \(e :: SomeException) -> do handler `catch` \(e :: SomeException) -> do
liftIO $ putStrLn (displayException e) Log.logError (pShow $ displayException e)
Twain.send $ Twain.send $
-- not necessarily 500, but we'll do this for now.
Twain.status Twain.status500 $ Twain.status Twain.status500 $
Twain.text "Internal Server Error 500" Twain.text "Internal Server Error 500"

View file

@ -17,7 +17,6 @@ import Web.Twain qualified as Twain
import Data.Text qualified as T import Data.Text qualified as T
import Network.Wai qualified as Wai import Network.Wai qualified as Wai
import Network.HTTP.Types.URI qualified as HTTP import Network.HTTP.Types.URI qualified as HTTP
import Control.Monad.IO.Class (liftIO)
import Text.ParserCombinators.ReadP qualified as P import Text.ParserCombinators.ReadP qualified as P
import Data.Text.Encoding qualified as T import Data.Text.Encoding qualified as T
import Data.ByteString qualified as BS import Data.ByteString qualified as BS
@ -25,6 +24,7 @@ import Data.ByteString.Lazy qualified as BSL
import Fedi.Crypto import Fedi.Crypto
import Fedi.Signature.Types import Fedi.Signature.Types
import Data.CaseInsensitive qualified as CI import Data.CaseInsensitive qualified as CI
import Control.Monad.IO.Class
-- * Check -- * Check
@ -83,7 +83,7 @@ makeSignatureString request (map (T.encodeUtf8 . T.toLower) -> headers) = do
BS.intercalate "\n" <$> traverse mylookup headers BS.intercalate "\n" <$> traverse mylookup headers
checkSignature checkSignature
:: MonadThrow m :: MonadIO m => MonadThrow m
=> Url -> ByteString -> SignatureHeader -> ByteString -> ByteString -> ByteString -> m () => Url -> ByteString -> SignatureHeader -> ByteString -> ByteString -> ByteString -> m ()
checkSignature personPkid personPublicKey sigheader signatureString digest body = do checkSignature personPkid personPublicKey sigheader signatureString digest body = do
-- check -- check