~vpzom/lotide

ref: a180781e02e14910f367bf2c704b84103e84b8c2 lotide/src/routes/well_known.rs -rw-r--r-- 5.6 KiB
a180781e — Colin Reeder Include score and optionally your_vote in every post list (#109) 4 months ago
                                                                                
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
170
use crate::{ActorLocalRef, CommunityLocalID, UserLocalID};
use serde_derive::{Deserialize, Serialize};
use std::borrow::Cow;
use std::sync::Arc;

pub fn route_well_known() -> crate::RouteNode<()> {
    crate::RouteNode::new()
        .with_child(
            "nodeinfo",
            crate::RouteNode::new().with_handler_async("GET", handler_nodeinfo_get),
        )
        .with_child(
            "webfinger",
            crate::RouteNode::new().with_handler_async("GET", handler_webfinger_get),
        )
}

async fn handler_nodeinfo_get(
    _: (),
    ctx: Arc<crate::RouteContext>,
    _req: hyper::Request<hyper::Body>,
) -> Result<hyper::Response<hyper::Body>, crate::Error> {
    let body = serde_json::to_vec(&serde_json::json!({
        "links": [
            {
                "rel": "http://nodeinfo.diaspora.software/ns/schema/2.0",
                "href": format!("{}/unstable/nodeinfo/2.0", ctx.host_url_api),
            }
        ]
    }))?
    .into();

    Ok(hyper::Response::builder()
        .header(hyper::header::CONTENT_TYPE, "application/jrd+json")
        .body(body)?)
}

#[derive(Deserialize, Serialize, Debug)]
pub struct FingerRequestQuery<'a> {
    pub resource: Cow<'a, str>,
    pub rel: Option<Cow<'a, str>>,
}

#[derive(Deserialize, Serialize, Debug)]
pub struct FingerLink<'a> {
    pub rel: Cow<'a, str>,
    #[serde(rename = "type")]
    pub type_: Option<Cow<'a, str>>,
    pub href: Option<Cow<'a, str>>,
}

#[derive(Deserialize, Serialize, Debug)]
pub struct FingerResponse<'a> {
    pub subject: Cow<'a, str>,
    #[serde(default)]
    pub aliases: Vec<Cow<'a, str>>,
    #[serde(default)]
    pub links: Vec<FingerLink<'a>>,
}

async fn handler_webfinger_get(
    _: (),
    ctx: Arc<crate::RouteContext>,
    req: hyper::Request<hyper::Body>,
) -> Result<hyper::Response<hyper::Body>, crate::Error> {
    let query: FingerRequestQuery<'_> =
        serde_urlencoded::from_str(req.uri().query().unwrap_or(""))?;

    enum LocalRef<'a> {
        UserID(UserLocalID),
        CommunityID(CommunityLocalID),
        Name(&'a str),
    }

    let found_ref =
        if let Some(rest) = crate::apub_util::try_strip_host(&query.resource, &ctx.host_url_apub) {
            if rest.starts_with("/users/") {
                if let Ok(id) = rest[7..].parse() {
                    Some(LocalRef::UserID(id))
                } else {
                    None
                }
            } else if rest.starts_with("/communities/") {
                if let Ok(id) = rest[13..].parse() {
                    Some(LocalRef::CommunityID(id))
                } else {
                    None
                }
            } else {
                None
            }
        } else if query.resource.starts_with("acct:")
            && query
                .resource
                .ends_with(&format!("@{}", ctx.local_hostname))
        {
            let name = &query.resource[5..(query.resource.len() - (ctx.local_hostname.len() + 1))];

            Some(LocalRef::Name(name))
        } else {
            None
        };

    let db = ctx.db_pool.get().await?;

    let found: Option<(ActorLocalRef, Cow<'_, str>)> = match found_ref {
        Some(LocalRef::UserID(id)) => {
            let row = db
                .query_opt("SELECT username FROM person WHERE id=$1 AND local", &[&id])
                .await?;
            row.map(|row| (ActorLocalRef::Person(id), Cow::Owned(row.get(0))))
        }
        Some(LocalRef::CommunityID(id)) => {
            let row = db
                .query_opt("SELECT name FROM community WHERE id=$1 AND local", &[&id])
                .await?;
            row.map(|row| (ActorLocalRef::Community(id), Cow::Owned(row.get(0))))
        }
        Some(LocalRef::Name(name)) => {
            let row = db.query_opt("(SELECT FALSE, id, username FROM person WHERE LOWER(username)=LOWER($1) AND local) UNION ALL (SELECT TRUE, id, name FROM community WHERE LOWER(name)=LOWER($1) AND local) LIMIT 1", &[&name]).await?;
            row.map(|row| {
                let id = row.get(1);
                (
                    if row.get(0) {
                        ActorLocalRef::Community(CommunityLocalID(id))
                    } else {
                        ActorLocalRef::Person(UserLocalID(id))
                    },
                    Cow::Owned(row.get(2)),
                )
            })
        }
        None => None,
    };

    Ok(match found {
        None => {
            crate::simple_response(hyper::StatusCode::NOT_FOUND, "Nothing found for that query")
        }
        Some((actor_ref, name)) => {
            let subject = format!("acct:{}@{}", name, ctx.local_hostname);
            let alias = match actor_ref {
                ActorLocalRef::Person(id) => {
                    crate::apub_util::get_local_person_apub_id(id, &ctx.host_url_apub)
                }
                ActorLocalRef::Community(id) => {
                    crate::apub_util::get_local_community_apub_id(id, &ctx.host_url_apub)
                }
            };
            let alias = alias.as_str();

            let body = FingerResponse {
                subject: subject.into(),
                aliases: vec![alias.into()],
                links: vec![FingerLink {
                    rel: "self".into(),
                    type_: Some(crate::apub_util::ACTIVITY_TYPE.into()),
                    href: Some(alias.into()),
                }],
            };

            let body = serde_json::to_vec(&body)?;
            let body = body.into();

            hyper::Response::builder()
                .header(hyper::header::CONTENT_TYPE, "application/jrd+json")
                .body(body)?
        }
    })
}