summaryrefslogtreecommitdiffstats
path: root/src/entity/user.rs
blob: da22f00bca9f13b38ef7c251939c81f0cf4d9822 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
use activitypub_federation::{
    config::Data,
    fetch::object_id::ObjectId,
    http_signatures::generate_actor_keypair,
    kinds::actor::PersonType,
    protocol::public_key::PublicKey,
    traits::{Actor, Object},
};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use stack_up::Services;
use tracing::trace;
use url::Url;
use uuid::Uuid;

use crate::{error::AppError, state::AppHandle};

#[derive(PartialEq, Clone, Debug)]
pub(crate) struct User {
    pub id: String,
    pub username: String,
    pub ap_id: ObjectId<User>,
    pub private_key: Option<String>,
    pub public_key: String,
    pub inbox: Url,
    pub outbox: Option<Url>,
}

pub struct DbUser {
    pub id: String,
    pub username: String,
    pub ap_id: String,
    pub private_key: Option<String>,
    pub public_key: String,
    pub inbox: String,
    pub outbox: Option<String>,
    pub local: bool,
}

impl TryFrom<DbUser> for User {
    type Error = AppError;
    fn try_from(value: DbUser) -> Result<Self, Self::Error> {
        Ok(Self {
            id: value.id,
            username: value.username,
            ap_id: Url::parse(&value.ap_id)?.into(),
            private_key: value.private_key,
            public_key: value.public_key,
            inbox: Url::parse(&value.inbox)?,
            outbox: match value.outbox {
                Some(ref url) => Some(Url::parse(url)?),
                None => None,
            },
        })
    }
}

impl User {
    pub async fn new(username: &str, services: &Services) -> Result<Self, AppError> {
        trace!("creating keypair for new user");
        let keys = generate_actor_keypair()?;
        let stub = &format!("http://localhost/users/{username}");
        let id = Uuid::now_v7();

        trace!(id = ?id, "creating a new user");
        let user = sqlx::query_as!(
            DbUser,
            "insert into account (id, username, ap_id, private_key, public_key, inbox, outbox, local) values ($1, $2, $3, $4, $5, $6, $7, $8) returning *",
            id,
            username,
            stub,
            keys.private_key,
            keys.public_key,
            &format!("{stub}/inbox"),
            &format!("{stub}/outbox"),
            true
        ).fetch_one(&services.postgres).await?;
        Self::try_from(user)
    }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Person {
    #[serde(rename = "type")]
    kind: PersonType,
    preferred_username: String,
    id: ObjectId<User>,
    inbox: Url,
    public_key: PublicKey,
    #[serde(skip_serializing_if = "Option::is_none")]
    outbox: Option<Url>,
}

#[async_trait]
impl Object for User {
    #[doc = " App data type passed to handlers. Must be identical to"]
    #[doc = " [crate::config::FederationConfigBuilder::app_data] type."]
    type DataType = AppHandle;

    #[doc = " The type of protocol struct which gets sent over network to federate this database struct."]
    type Kind = Person;

    #[doc = " Error type returned by handler methods"]
    type Error = AppError;

    #[doc = " `id` field of the object"]
    fn id(&self) -> &Url {
        self.ap_id.inner()
    }

    #[doc = " Try to read the object with given `id` from local database."]
    #[doc = " Should return `Ok(None)` if not found."]
    async fn read_from_id(
        object_id: Url,
        data: &Data<Self::DataType>,
    ) -> Result<Option<Self>, Self::Error> {
        todo!()
    }

    #[doc = " Convert database type to Activitypub type."]
    #[doc = " Called when a local object gets fetched by another instance over HTTP, or when an object"]
    #[doc = " gets sent in an activity."]
    async fn into_json(self, data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
        Ok(Person {
            preferred_username: self.username.clone(),
            kind: Default::default(),
            id: self.ap_id.clone(),
            inbox: self.inbox.clone(),
            public_key: self.public_key(),
            outbox: self.outbox.clone(),
        })
    }

    #[doc = " Verifies that the received object is valid."]
    #[doc = " You should check here that the domain of id matches `expected_domain`. Additionally you"]
    #[doc = " should perform any application specific checks."]
    #[doc = " It is necessary to use a separate method for this, because it might be used for activities"]
    #[doc = " like `Delete/Note`, which shouldn\'t perform any database write for the inner `Note`."]
    async fn verify(
        json: &Self::Kind,
        expected_domain: &Url,
        data: &Data<Self::DataType>,
    ) -> Result<(), Self::Error> {
        todo!()
    }

    #[doc = " Convert object from ActivityPub type to database type."]
    #[doc = " Called when an object is received from HTTP fetch or as part of an activity. This method"]
    #[doc = " should write the received object to database. Note that there is no distinction between"]
    #[doc = " create and update, so an `upsert` operation should be used."]
    async fn from_json(json: Self::Kind, data: &Data<Self::DataType>) -> Result<Self, Self::Error> {
        todo!()
    }
}

impl Actor for User {
    fn public_key_pem(&self) -> &str {
        &self.public_key
    }

    fn private_key_pem(&self) -> Option<String> {
        self.private_key.clone()
    }

    fn inbox(&self) -> Url {
        self.inbox.clone()
    }
}