Support reaching user@sub.domain.tld at user@domain.tld (#134)
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/134 Co-authored-by: Joel Beckmeyer <joel@beckmeyer.us> Co-committed-by: Joel Beckmeyer <joel@beckmeyer.us>
This commit is contained in:
parent
c3eea8dc7d
commit
e26388a01c
20 changed files with 489 additions and 65 deletions
|
@ -173,6 +173,7 @@ you might end up in a situation where you don't have an ability to get it.
|
|||
- Attachment dimensions and blurhashes are federated when available.
|
||||
- Mastodon API: support `poll` notification.
|
||||
- Pinned posts federation
|
||||
- Possibility to discover users like `user@example.org`, while Akkoma is working on `akkoma.example.org`. Additional configuration required.
|
||||
|
||||
### Fixed
|
||||
- Don't crash so hard when email settings are invalid.
|
||||
|
|
|
@ -809,6 +809,8 @@
|
|||
{Pleroma.Search, [max_running: 30, max_waiting: 50]}
|
||||
]
|
||||
|
||||
config :pleroma, Pleroma.Web.WebFinger, domain: nil, update_nickname_on_user_fetch: true
|
||||
|
||||
config :pleroma, Pleroma.Search, module: Pleroma.Search.DatabaseSearch
|
||||
|
||||
config :pleroma, Pleroma.Search.Meilisearch,
|
||||
|
|
|
@ -126,6 +126,8 @@
|
|||
|
||||
config :pleroma, :cachex, provider: Pleroma.CachexMock
|
||||
|
||||
config :pleroma, Pleroma.Web.WebFinger, update_nickname_on_user_fetch: false
|
||||
|
||||
config :pleroma, :side_effects,
|
||||
ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock,
|
||||
logger: Pleroma.LoggerMock
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
# How to use a different domain name for Akkoma and the users it serves
|
||||
|
||||
Akkoma users are primarily identified by a `user@example.org` handle, and you might want this identifier to be the same as your email or jabber account, for instance.
|
||||
However, in this case, you are almost certainly serving some web content on `https://example.org` already, and you might want to use another domain (say `akkoma.example.org`) for Akkoma itself.
|
||||
|
||||
Akkoma supports that, but it might be tricky to set up, and any error might prevent you from federating with other instances.
|
||||
|
||||
*If you are already running Akkoma on `example.org`, it is no longer possible to move it to `akkoma.example.org`.*
|
||||
|
||||
## Account identifiers
|
||||
|
||||
It is important to understand that for federation purposes, a user in Akkoma has two unique identifiers associated:
|
||||
|
||||
- A webfinger `acct:` URI, used for discovery and as a verifiable global name for the user across Akkoma instances. In our example, our account's acct: URI is `acct:user@example.org`
|
||||
- An author/actor URI, used in every other aspect of federation. This is the way in which users are identified in ActivityPub, the underlying protocol used for federation with other Akkoma instances.
|
||||
In our case, it is `https://akkoma.example.org/users/user`.
|
||||
|
||||
Both account identifiers are unique and required for Akkoma. An important risk if you set up your Akkoma instance incorrectly is to create two users (with different acct: URIs) with conflicting author/actor URIs.
|
||||
|
||||
## WebFinger
|
||||
|
||||
As said earlier, each Akkoma user has an `acct`: URI, which is used for discovery and authentication. When you add @user@example.org, a webfinger query is performed. This is done in two steps:
|
||||
|
||||
1. Querying `https://example.org/.well-known/host-meta` (where the domain of the URL matches the domain part of the `acct`: URI) to get information on how to perform the query.
|
||||
This file will indeed contain a URL template of the form `https://example.org/.well-known/webfinger?resource={uri}` that will be used in the second step.
|
||||
2. Fill the returned template with the `acct`: URI to be queried and perform the query: `https://example.org/.well-known/webfinger?resource=acct:user@example.org`
|
||||
|
||||
## Configuring your Akkoma instance
|
||||
|
||||
**_DO NOT ATTEMPT TO CONFIGURE YOUR INSTANCE THIS WAY IF YOU DID NOT UNDERSTAND THE ABOVE_**
|
||||
|
||||
### Configuring Akkoma
|
||||
|
||||
Akkoma has a two configuration settings to enable using different domains for your users and Akkoma itself. `host` in `Pleroma.Web.Endpoint` and `domain` in `Pleroma.Web.WebFinger`. When the latter is not set, it defaults to the value of `host`.
|
||||
|
||||
*Be extra careful when configuring your Akkoma instance, as changing `host` may cause remote instances to register different accounts with the same author/actor URI, which will result in federation issues!*
|
||||
|
||||
```elixir
|
||||
config :pleroma, Pleroma.Web.Endpoint,
|
||||
url: [host: "pleroma.example.org"]
|
||||
|
||||
config :pleroma, Pleroma.Web.WebFinger, domain: "example.org"
|
||||
```
|
||||
|
||||
- `domain` - is the domain for which your Akkoma instance has authority, it's the domain used in `acct:` URI. In our example, `domain` would be set to `example.org`.
|
||||
- `host` - is the domain used for any URL generated for your instance, including the author/actor URL's. In our case, that would be `akkoma.example.org`.
|
||||
|
||||
### Configuring WebFinger domain
|
||||
|
||||
Now, you have Akkoma running at `https://akkoma.example.org` as well as a website at `https://example.org`. If you recall how webfinger queries work, the first step is to query `https://example.org/.well-known/host-meta`, which will contain an URL template.
|
||||
|
||||
Therefore, the easiest way to configure `example.org` is to redirect `/.well-known/host-meta` to `akkoma.example.org`.
|
||||
|
||||
With nginx, it would be as simple as adding:
|
||||
|
||||
```nginx
|
||||
location = /.well-known/host-meta {
|
||||
return 301 https://akkoma.example.org$request_uri;
|
||||
}
|
||||
```
|
||||
|
||||
in example.org's server block.
|
|
@ -83,4 +83,13 @@ defp build_request(method, headers, options, url, body, params) do
|
|||
|> Builder.add_param(:query, :query, params)
|
||||
|> Builder.convert_to_keyword()
|
||||
end
|
||||
|
||||
defp adapter_middlewares(_) do
|
||||
if Pleroma.Config.get(:env) == :test do
|
||||
# Emulate redirects in test env, which are handled by adapters in other environments
|
||||
[Tesla.Middleware.FollowRedirects]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1437,7 +1437,7 @@ defp normalize_image(%{"url" => url}) do
|
|||
defp normalize_image(urls) when is_list(urls), do: urls |> List.first() |> normalize_image()
|
||||
defp normalize_image(_), do: nil
|
||||
|
||||
defp object_to_user_data(data) do
|
||||
defp object_to_user_data(data, additional) do
|
||||
fields =
|
||||
data
|
||||
|> Map.get("attachment", [])
|
||||
|
@ -1467,18 +1467,18 @@ defp object_to_user_data(data) do
|
|||
public_key =
|
||||
if is_map(data["publicKey"]) && is_binary(data["publicKey"]["publicKeyPem"]) do
|
||||
data["publicKey"]["publicKeyPem"]
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
shared_inbox =
|
||||
if is_map(data["endpoints"]) && is_binary(data["endpoints"]["sharedInbox"]) do
|
||||
data["endpoints"]["sharedInbox"]
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
user_data = %{
|
||||
# if WebFinger request was already done, we probably have acct, otherwise
|
||||
# we request WebFinger here
|
||||
nickname = additional[:nickname_from_acct] || generate_nickname(data)
|
||||
|
||||
%{
|
||||
ap_id: data["id"],
|
||||
uri: get_actor_url(data["url"]),
|
||||
ap_enabled: true,
|
||||
|
@ -1499,21 +1499,27 @@ defp object_to_user_data(data) do
|
|||
public_key: public_key,
|
||||
inbox: data["inbox"],
|
||||
shared_inbox: shared_inbox,
|
||||
pinned_objects: pinned_objects
|
||||
pinned_objects: pinned_objects,
|
||||
nickname: nickname
|
||||
}
|
||||
end
|
||||
|
||||
# nickname can be nil because of virtual actors
|
||||
if data["preferredUsername"] do
|
||||
Map.put(
|
||||
user_data,
|
||||
:nickname,
|
||||
"#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}"
|
||||
)
|
||||
defp generate_nickname(%{"preferredUsername" => username} = data) when is_binary(username) do
|
||||
generated = "#{username}@#{URI.parse(data["id"]).host}"
|
||||
|
||||
if Config.get([WebFinger, :update_nickname_on_user_fetch]) do
|
||||
case WebFinger.finger(generated) do
|
||||
{:ok, %{"subject" => "acct:" <> acct}} -> acct
|
||||
_ -> generated
|
||||
end
|
||||
else
|
||||
Map.put(user_data, :nickname, nil)
|
||||
generated
|
||||
end
|
||||
end
|
||||
|
||||
# nickname can be nil because of virtual actors
|
||||
defp generate_nickname(_), do: nil
|
||||
|
||||
def fetch_follow_information_for_user(user) do
|
||||
with {:ok, following_data} <-
|
||||
Fetcher.fetch_and_contain_remote_object_from_id(user.following_address),
|
||||
|
@ -1585,17 +1591,17 @@ defp collection_private(%{"first" => first}) do
|
|||
|
||||
defp collection_private(_data), do: {:ok, true}
|
||||
|
||||
def user_data_from_user_object(data) do
|
||||
def user_data_from_user_object(data, additional \\ []) do
|
||||
with {:ok, data} <- MRF.filter(data) do
|
||||
{:ok, object_to_user_data(data)}
|
||||
{:ok, object_to_user_data(data, additional)}
|
||||
else
|
||||
e -> {:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||
def fetch_and_prepare_user_from_ap_id(ap_id, additional \\ []) do
|
||||
with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
|
||||
{:ok, data} <- user_data_from_user_object(data) do
|
||||
{:ok, data} <- user_data_from_user_object(data, additional) do
|
||||
{:ok, maybe_update_follow_information(data)}
|
||||
else
|
||||
# If this has been deleted, only log a debug and not an error
|
||||
|
@ -1693,13 +1699,13 @@ def pinned_fetch_task(%{pinned_objects: pins}) do
|
|||
end
|
||||
end
|
||||
|
||||
def make_user_from_ap_id(ap_id) do
|
||||
def make_user_from_ap_id(ap_id, additional \\ []) do
|
||||
user = User.get_cached_by_ap_id(ap_id)
|
||||
|
||||
if user && !User.ap_enabled?(user) do
|
||||
Transmogrifier.upgrade_user_from_ap_id(ap_id)
|
||||
else
|
||||
with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||
with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, additional) do
|
||||
{:ok, _pid} = Task.start(fn -> pinned_fetch_task(data) end)
|
||||
|
||||
if user do
|
||||
|
@ -1719,8 +1725,9 @@ def make_user_from_ap_id(ap_id) do
|
|||
end
|
||||
|
||||
def make_user_from_nickname(nickname) do
|
||||
with {:ok, %{"ap_id" => ap_id}} when not is_nil(ap_id) <- WebFinger.finger(nickname) do
|
||||
make_user_from_ap_id(ap_id)
|
||||
with {:ok, %{"ap_id" => ap_id, "subject" => "acct:" <> acct}} when not is_nil(ap_id) <-
|
||||
WebFinger.finger(nickname) do
|
||||
make_user_from_ap_id(ap_id, nickname_from_acct: acct)
|
||||
else
|
||||
_e -> {:error, "No AP id in WebFinger"}
|
||||
end
|
||||
|
|
|
@ -32,7 +32,13 @@ def host_meta do
|
|||
|
||||
def webfinger(resource, fmt) when fmt in ["XML", "JSON"] do
|
||||
host = Pleroma.Web.Endpoint.host()
|
||||
regex = ~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@#{host}/
|
||||
|
||||
regex =
|
||||
if webfinger_domain = Pleroma.Config.get([__MODULE__, :domain]) do
|
||||
~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@(#{host}|#{webfinger_domain})/
|
||||
else
|
||||
~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@#{host}/
|
||||
end
|
||||
|
||||
with %{"username" => username} <- Regex.named_captures(regex, resource),
|
||||
%User{} = user <- User.get_cached_by_nickname(username) do
|
||||
|
@ -66,7 +72,7 @@ def represent_user(user, "JSON") do
|
|||
{:ok, user} = User.ensure_keys_present(user)
|
||||
|
||||
%{
|
||||
"subject" => "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}",
|
||||
"subject" => "acct:#{user.nickname}@#{domain()}",
|
||||
"aliases" => gather_aliases(user),
|
||||
"links" => gather_links(user)
|
||||
}
|
||||
|
@ -88,12 +94,16 @@ def represent_user(user, "XML") do
|
|||
:XRD,
|
||||
%{xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0"},
|
||||
[
|
||||
{:Subject, "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}"}
|
||||
{:Subject, "acct:#{user.nickname}@#{domain()}"}
|
||||
] ++ aliases ++ links
|
||||
}
|
||||
|> XmlBuilder.to_doc()
|
||||
end
|
||||
|
||||
defp domain do
|
||||
Pleroma.Config.get([__MODULE__, :domain]) || Pleroma.Web.Endpoint.host()
|
||||
end
|
||||
|
||||
defp webfinger_from_xml(body) do
|
||||
with {:ok, doc} <- XML.parse_document(body) do
|
||||
subject = XML.string_from_xpath("//Subject", doc)
|
||||
|
@ -150,17 +160,15 @@ def get_template_from_xml(body) do
|
|||
end
|
||||
|
||||
def find_lrdd_template(domain) do
|
||||
with {:ok, %{status: status, body: body}} when status in 200..299 <-
|
||||
HTTP.get("http://#{domain}/.well-known/host-meta") do
|
||||
# WebFinger is restricted to HTTPS - https://tools.ietf.org/html/rfc7033#section-9.1
|
||||
meta_url = "https://#{domain}/.well-known/host-meta"
|
||||
|
||||
with {:ok, %{status: status, body: body}} when status in 200..299 <- HTTP.get(meta_url) do
|
||||
get_template_from_xml(body)
|
||||
else
|
||||
_ ->
|
||||
with {:ok, %{body: body, status: status}} when status in 200..299 <-
|
||||
HTTP.get("https://#{domain}/.well-known/host-meta") do
|
||||
get_template_from_xml(body)
|
||||
else
|
||||
e -> {:error, "Can't find LRDD template: #{inspect(e)}"}
|
||||
end
|
||||
error ->
|
||||
Logger.warn("Can't find LRDD template in #{inspect(meta_url)}: #{inspect(error)}")
|
||||
{:error, :lrdd_not_found}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -174,7 +182,7 @@ defp get_address_from_domain(domain, encoded_account) when is_binary(domain) do
|
|||
end
|
||||
end
|
||||
|
||||
defp get_address_from_domain(_, _), do: nil
|
||||
defp get_address_from_domain(_, _), do: {:error, :webfinger_no_domain}
|
||||
|
||||
@spec finger(String.t()) :: {:ok, map()} | {:error, any()}
|
||||
def finger(account) do
|
||||
|
@ -191,13 +199,11 @@ def finger(account) do
|
|||
encoded_account = URI.encode("acct:#{account}")
|
||||
|
||||
with address when is_binary(address) <- get_address_from_domain(domain, encoded_account),
|
||||
response <-
|
||||
{:ok, %{status: status, body: body, headers: headers}} when status in 200..299 <-
|
||||
HTTP.get(
|
||||
address,
|
||||
[{"accept", "application/xrd+xml,application/jrd+json"}]
|
||||
),
|
||||
{:ok, %{status: status, body: body, headers: headers}} when status in 200..299 <-
|
||||
response do
|
||||
) do
|
||||
case List.keyfind(headers, "content-type", 0) do
|
||||
{_, content_type} ->
|
||||
case Plug.Conn.Utils.media_type(content_type) do
|
||||
|
@ -215,10 +221,9 @@ def finger(account) do
|
|||
{:error, {:content_type, nil}}
|
||||
end
|
||||
else
|
||||
e ->
|
||||
Logger.debug(fn -> "Couldn't finger #{account}" end)
|
||||
Logger.debug(fn -> inspect(e) end)
|
||||
{:error, e}
|
||||
error ->
|
||||
Logger.debug("Couldn't finger #{account}: #{inspect(error)}")
|
||||
error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><hm:Host xmlns:hm="http://host-meta.net/xrd/1.0">framatube.org</hm:Host><Link rel="lrdd" template="http://framatube.org/main/xrd?uri={uri}"><Title>Resource Descriptor</Title></Link></XRD>
|
||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><hm:Host xmlns:hm="http://host-meta.net/xrd/1.0">framatube.org</hm:Host><Link rel="lrdd" template="https://framatube.org/main/xrd?uri={uri}"><Title>Resource Descriptor</Title></Link></XRD>
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><hm:Host xmlns:hm="http://host-meta.net/xrd/1.0">status.alpicola.com</hm:Host><Link rel="lrdd" template="http://status.alpicola.com/main/xrd?uri={uri}"><Title>Resource Descriptor</Title></Link></XRD>
|
||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><hm:Host xmlns:hm="http://host-meta.net/xrd/1.0">status.alpicola.com</hm:Host><Link rel="lrdd" template="https://status.alpicola.com/main/xrd?uri={uri}"><Title>Resource Descriptor</Title></Link></XRD>
|
||||
|
|
4
test/fixtures/webfinger/masto-host-meta.xml
vendored
Normal file
4
test/fixtures/webfinger/masto-host-meta.xml
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
|
||||
<Link rel="lrdd" template="https://{{domain}}/.well-known/webfinger?resource={uri}"/>
|
||||
</XRD>
|
92
test/fixtures/webfinger/masto-user.json
vendored
Normal file
92
test/fixtures/webfinger/masto-user.json
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"featured": {
|
||||
"@id": "toot:featured",
|
||||
"@type": "@id"
|
||||
},
|
||||
"featuredTags": {
|
||||
"@id": "toot:featuredTags",
|
||||
"@type": "@id"
|
||||
},
|
||||
"alsoKnownAs": {
|
||||
"@id": "as:alsoKnownAs",
|
||||
"@type": "@id"
|
||||
},
|
||||
"movedTo": {
|
||||
"@id": "as:movedTo",
|
||||
"@type": "@id"
|
||||
},
|
||||
"schema": "http://schema.org#",
|
||||
"PropertyValue": "schema:PropertyValue",
|
||||
"value": "schema:value",
|
||||
"IdentityProof": "toot:IdentityProof",
|
||||
"discoverable": "toot:discoverable",
|
||||
"Device": "toot:Device",
|
||||
"Ed25519Signature": "toot:Ed25519Signature",
|
||||
"Ed25519Key": "toot:Ed25519Key",
|
||||
"Curve25519Key": "toot:Curve25519Key",
|
||||
"EncryptedMessage": "toot:EncryptedMessage",
|
||||
"publicKeyBase64": "toot:publicKeyBase64",
|
||||
"deviceId": "toot:deviceId",
|
||||
"claim": {
|
||||
"@type": "@id",
|
||||
"@id": "toot:claim"
|
||||
},
|
||||
"fingerprintKey": {
|
||||
"@type": "@id",
|
||||
"@id": "toot:fingerprintKey"
|
||||
},
|
||||
"identityKey": {
|
||||
"@type": "@id",
|
||||
"@id": "toot:identityKey"
|
||||
},
|
||||
"devices": {
|
||||
"@type": "@id",
|
||||
"@id": "toot:devices"
|
||||
},
|
||||
"messageFranking": "toot:messageFranking",
|
||||
"messageType": "toot:messageType",
|
||||
"cipherText": "toot:cipherText",
|
||||
"suspended": "toot:suspended",
|
||||
"focalPoint": {
|
||||
"@container": "@list",
|
||||
"@id": "toot:focalPoint"
|
||||
}
|
||||
}
|
||||
],
|
||||
"id": "https://{{domain}}/users/{{nickname}}",
|
||||
"type": "Person",
|
||||
"following": "https://{{domain}}/users/{{nickname}}/following",
|
||||
"followers": "https://{{domain}}/users/{{nickname}}/followers",
|
||||
"inbox": "https://{{domain}}/users/{{nickname}}/inbox",
|
||||
"outbox": "https://{{domain}}/users/{{nickname}}/outbox",
|
||||
"featured": "https://{{domain}}/users/{{nickname}}/collections/featured",
|
||||
"featuredTags": "https://{{domain}}/users/{{nickname}}/collections/tags",
|
||||
"preferredUsername": "{{nickname}}",
|
||||
"name": "Name Name",
|
||||
"summary": "<p>Summary</p>",
|
||||
"url": "https://{{domain}}/@{{nickname}}",
|
||||
"manuallyApprovesFollowers": false,
|
||||
"discoverable": false,
|
||||
"devices": "https://{{domain}}/users/{{nickname}}/collections/devices",
|
||||
"publicKey": {
|
||||
"id": "https://{{domain}}/users/{{nickname}}#main-key",
|
||||
"owner": "https://{{domain}}/users/{{nickname}}",
|
||||
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvwDujxmxoYHs64MyVB3L\nG5ZyBxV3ufaMRBFu42bkcTpISq1WwZ+3Zb6CI8zOO+nM+Q2llrVRYjZa4ZFnOLvM\nTq/Kf+Zf5wy2aCRer88gX+MsJOAtItSi412y0a/rKOuFaDYLOLeTkRvmGLgZWbsr\nZJOp+YWb3zQ5qsIOInkc5BwI172tMsGeFtsnbNApPV4lrmtTGaJ8RiM8MR7XANBO\nfOHggSt1+eAIKGIsCmINEMzs1mG9D75xKtC/sM8GfbvBclQcBstGkHAEj1VHPW0c\nh6Bok5/QQppicyb8UA1PAA9bznSFtKlYE4xCH8rlCDSDTBRtdnBWHKcj619Ujz4Q\nawIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||
},
|
||||
"tag": [],
|
||||
"attachment": [],
|
||||
"endpoints": {
|
||||
"sharedInbox": "https://{{domain}}/inbox"
|
||||
},
|
||||
"icon": {
|
||||
"type": "Image",
|
||||
"mediaType": "image/jpeg",
|
||||
"url": "https://s3.wasabisys.com/merp/accounts/avatars/000/000/001/original/6fdd3eee632af247.jpg"
|
||||
}
|
||||
}
|
23
test/fixtures/webfinger/masto-webfinger.json
vendored
Normal file
23
test/fixtures/webfinger/masto-webfinger.json
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"subject": "acct:{{nickname}}@{{domain}}",
|
||||
"aliases": [
|
||||
"https://{{subdomain}}/@{{nickname}}",
|
||||
"https://{{subdomain}}/users/{{nickname}}"
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"rel": "http://webfinger.net/rel/profile-page",
|
||||
"type": "text/html",
|
||||
"href": "https://{{subdomain}}/@{{nickname}}"
|
||||
},
|
||||
{
|
||||
"rel": "self",
|
||||
"type": "application/activity+json",
|
||||
"href": "https://{{subdomain}}/users/{{nickname}}"
|
||||
},
|
||||
{
|
||||
"rel": "http://ostatus.org/schema/1.0/subscribe",
|
||||
"template": "https://{{subdomain}}/authorize_interaction?uri={uri}"
|
||||
}
|
||||
]
|
||||
}
|
1
test/fixtures/webfinger/pleroma-host-meta.xml
vendored
Normal file
1
test/fixtures/webfinger/pleroma-host-meta.xml
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" template="https://{{domain}}/.well-known/webfinger?resource={uri}" type="application/xrd+xml" /></XRD>
|
58
test/fixtures/webfinger/pleroma-user.json
vendored
Normal file
58
test/fixtures/webfinger/pleroma-user.json
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://{{domain}}/schemas/litepub-0.1.jsonld",
|
||||
{
|
||||
"@language": "und"
|
||||
}
|
||||
],
|
||||
"alsoKnownAs": [],
|
||||
"attachment": [],
|
||||
"capabilities": {
|
||||
"acceptsChatMessages": true
|
||||
},
|
||||
"discoverable": true,
|
||||
"endpoints": {
|
||||
"oauthAuthorizationEndpoint": "https://{{domain}}/oauth/authorize",
|
||||
"oauthRegistrationEndpoint": "https://{{domain}}/api/v1/apps",
|
||||
"oauthTokenEndpoint": "https://{{domain}}/oauth/token",
|
||||
"sharedInbox": "https://{{domain}}/inbox",
|
||||
"uploadMedia": "https://{{domain}}/api/ap/upload_media"
|
||||
},
|
||||
"followers": "https://{{domain}}/users/{{nickname}}/followers",
|
||||
"following": "https://{{domain}}/users/{{nickname}}/following",
|
||||
"icon": {
|
||||
"type": "Image",
|
||||
"url": "https://{{domain}}/media/a932a27f158b63c3a97e3a57d5384f714a82249274c6fc66c9eca581b4fd8af2.jpg"
|
||||
},
|
||||
"id": "https://{{domain}}/users/{{nickname}}",
|
||||
"image": {
|
||||
"type": "Image",
|
||||
"url": "https://{{domain}}/media/db15f476d0ad14488db4762b7800479e6ef67b1824f8b9ea5c1fa05b7525c5b7.jpg"
|
||||
},
|
||||
"inbox": "https://{{domain}}/users/{{nickname}}/inbox",
|
||||
"manuallyApprovesFollowers": false,
|
||||
"name": "{{nickname}} :verified:",
|
||||
"outbox": "https://{{domain}}/users/{{nickname}}/outbox",
|
||||
"preferredUsername": "{{nickname}}",
|
||||
"publicKey": {
|
||||
"id": "https://{{domain}}/users/{{nickname}}#main-key",
|
||||
"owner": "https://{{domain}}/users/{{nickname}}",
|
||||
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu4XOAopC4nRIxNlHlt60\n//nCicuedu5wvLGIoQ+KUM2u7/PhLrrTDEqr1A7yQL95S0X8ryYtALgFLI5A54ww\nqjMIbIGAs44lEmDLMEd+XI+XxREE8wdsFpb4QQzWug0DTyqlMouTU25k0tfKh1rF\n4PMJ3uBSjDTAGgFvLNyFWTiVVgChbTNgGOmrEBucRl4NmKzQ69/FIUwENV88oQSU\n3bWvQTEH9rWH1rCLpkmQwdRiWfnhFX/4EUqXukfgoskvenKR8ff3nYhElDqFoE0e\nqUnIW1OZceyl8JewVLcL6m0/wdKeosTsfrcWc8DKfnRYQcBGNoBEq9GrOHDU0q2v\nyQIDAQAB\n-----END PUBLIC KEY-----\n\n"
|
||||
},
|
||||
"summary": "Pleroma BE dev",
|
||||
"tag": [
|
||||
{
|
||||
"icon": {
|
||||
"type": "Image",
|
||||
"url": "https://{{domain}}/emoji/mine/6143373a807b1ae7.png"
|
||||
},
|
||||
"id": "https://{{domain}}/emoji/mine/6143373a807b1ae7.png",
|
||||
"name": ":verified:",
|
||||
"type": "Emoji",
|
||||
"updated": "1970-01-01T00:00:00Z"
|
||||
}
|
||||
],
|
||||
"type": "Person",
|
||||
"url": "https://{{domain}}/users/{{nickname}}"
|
||||
}
|
27
test/fixtures/webfinger/pleroma-webfinger.json
vendored
Normal file
27
test/fixtures/webfinger/pleroma-webfinger.json
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"aliases": [
|
||||
"https://{{subdomain}}/users/{{nickname}}"
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"href": "https://{{subdomain}}/users/{{nickname}}",
|
||||
"rel": "http://webfinger.net/rel/profile-page",
|
||||
"type": "text/html"
|
||||
},
|
||||
{
|
||||
"href": "https://{{subdomain}}/users/{{nickname}}",
|
||||
"rel": "self",
|
||||
"type": "application/activity+json"
|
||||
},
|
||||
{
|
||||
"href": "https://{{subdomain}}/users/{{nickname}}",
|
||||
"rel": "self",
|
||||
"type": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
|
||||
},
|
||||
{
|
||||
"rel": "http://ostatus.org/schema/1.0/subscribe",
|
||||
"template": "https://{{subdomain}}/ostatus_subscribe?acct={uri}"
|
||||
}
|
||||
],
|
||||
"subject": "acct:{{nickname}}@{{domain}}"
|
||||
}
|
|
@ -738,6 +738,116 @@ test "gets an existing user by ap_id" do
|
|||
end
|
||||
end
|
||||
|
||||
describe "get_or_fetch/1 remote users with tld, while BE is runned on subdomain" do
|
||||
setup do: clear_config([Pleroma.Web.WebFinger, :update_nickname_on_user_fetch], true)
|
||||
|
||||
test "for mastodon" do
|
||||
Tesla.Mock.mock(fn
|
||||
%{url: "https://example.com/.well-known/host-meta"} ->
|
||||
%Tesla.Env{
|
||||
status: 302,
|
||||
headers: [{"location", "https://sub.example.com/.well-known/host-meta"}]
|
||||
}
|
||||
|
||||
%{url: "https://sub.example.com/.well-known/host-meta"} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
"test/fixtures/webfinger/masto-host-meta.xml"
|
||||
|> File.read!()
|
||||
|> String.replace("{{domain}}", "sub.example.com")
|
||||
}
|
||||
|
||||
%{url: "https://sub.example.com/.well-known/webfinger?resource=acct:a@example.com"} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
"test/fixtures/webfinger/masto-webfinger.json"
|
||||
|> File.read!()
|
||||
|> String.replace("{{nickname}}", "a")
|
||||
|> String.replace("{{domain}}", "example.com")
|
||||
|> String.replace("{{subdomain}}", "sub.example.com"),
|
||||
headers: [{"content-type", "application/jrd+json"}]
|
||||
}
|
||||
|
||||
%{url: "https://sub.example.com/users/a"} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
"test/fixtures/webfinger/masto-user.json"
|
||||
|> File.read!()
|
||||
|> String.replace("{{nickname}}", "a")
|
||||
|> String.replace("{{domain}}", "sub.example.com"),
|
||||
headers: [{"content-type", "application/activity+json"}]
|
||||
}
|
||||
|
||||
%{url: "https://sub.example.com/users/a/collections/featured"} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
File.read!("test/fixtures/users_mock/masto_featured.json")
|
||||
|> String.replace("{{domain}}", "sub.example.com")
|
||||
|> String.replace("{{nickname}}", "a"),
|
||||
headers: [{"content-type", "application/activity+json"}]
|
||||
}
|
||||
end)
|
||||
|
||||
ap_id = "a@example.com"
|
||||
{:ok, fetched_user} = User.get_or_fetch(ap_id)
|
||||
|
||||
assert fetched_user.ap_id == "https://sub.example.com/users/a"
|
||||
assert fetched_user.nickname == "a@example.com"
|
||||
end
|
||||
|
||||
test "for pleroma" do
|
||||
Tesla.Mock.mock(fn
|
||||
%{url: "https://example.com/.well-known/host-meta"} ->
|
||||
%Tesla.Env{
|
||||
status: 302,
|
||||
headers: [{"location", "https://sub.example.com/.well-known/host-meta"}]
|
||||
}
|
||||
|
||||
%{url: "https://sub.example.com/.well-known/host-meta"} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
"test/fixtures/webfinger/pleroma-host-meta.xml"
|
||||
|> File.read!()
|
||||
|> String.replace("{{domain}}", "sub.example.com")
|
||||
}
|
||||
|
||||
%{url: "https://sub.example.com/.well-known/webfinger?resource=acct:a@example.com"} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
"test/fixtures/webfinger/pleroma-webfinger.json"
|
||||
|> File.read!()
|
||||
|> String.replace("{{nickname}}", "a")
|
||||
|> String.replace("{{domain}}", "example.com")
|
||||
|> String.replace("{{subdomain}}", "sub.example.com"),
|
||||
headers: [{"content-type", "application/jrd+json"}]
|
||||
}
|
||||
|
||||
%{url: "https://sub.example.com/users/a"} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
"test/fixtures/webfinger/pleroma-user.json"
|
||||
|> File.read!()
|
||||
|> String.replace("{{nickname}}", "a")
|
||||
|> String.replace("{{domain}}", "sub.example.com"),
|
||||
headers: [{"content-type", "application/activity+json"}]
|
||||
}
|
||||
end)
|
||||
|
||||
ap_id = "a@example.com"
|
||||
{:ok, fetched_user} = User.get_or_fetch(ap_id)
|
||||
|
||||
assert fetched_user.ap_id == "https://sub.example.com/users/a"
|
||||
assert fetched_user.nickname == "a@example.com"
|
||||
end
|
||||
end
|
||||
|
||||
describe "fetching a user from nickname or trying to build one" do
|
||||
test "gets an existing user" do
|
||||
user = insert(:user)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do
|
||||
use Pleroma.Web.ConnCase
|
||||
use Pleroma.Web.ConnCase, async: true
|
||||
|
||||
alias Pleroma.MFA
|
||||
alias Pleroma.MFA.TOTP
|
||||
|
|
|
@ -48,6 +48,35 @@ test "Webfinger JRD" do
|
|||
]
|
||||
end
|
||||
|
||||
test "reach user on tld, while pleroma is runned on subdomain" do
|
||||
Pleroma.Web.Endpoint.config_change(
|
||||
[{Pleroma.Web.Endpoint, url: [host: "sub.example.com"]}],
|
||||
[]
|
||||
)
|
||||
|
||||
clear_config([Pleroma.Web.Endpoint, :url, :host], "sub.example.com")
|
||||
|
||||
clear_config([Pleroma.Web.WebFinger, :domain], "example.com")
|
||||
|
||||
user = insert(:user, ap_id: "https://sub.example.com/users/bobby", nickname: "bobby")
|
||||
|
||||
response =
|
||||
build_conn()
|
||||
|> put_req_header("accept", "application/jrd+json")
|
||||
|> get("/.well-known/webfinger?resource=acct:#{user.nickname}@example.com")
|
||||
|> json_response(200)
|
||||
|
||||
assert response["subject"] == "acct:#{user.nickname}@example.com"
|
||||
assert response["aliases"] == ["https://sub.example.com/users/#{user.nickname}"]
|
||||
|
||||
on_exit(fn ->
|
||||
Pleroma.Web.Endpoint.config_change(
|
||||
[{Pleroma.Web.Endpoint, url: [host: "localhost"]}],
|
||||
[]
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
test "it returns 404 when user isn't found (JSON)" do
|
||||
result =
|
||||
build_conn()
|
||||
|
|
|
@ -47,7 +47,7 @@ test "returns error for nonsensical input" do
|
|||
|
||||
test "returns error when there is no content-type header" do
|
||||
Tesla.Mock.mock(fn
|
||||
%{url: "http://social.heldscal.la/.well-known/host-meta"} ->
|
||||
%{url: "https://social.heldscal.la/.well-known/host-meta"} ->
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
|
@ -120,7 +120,7 @@ test "it gets the xrd endpoint for hubzilla" do
|
|||
test "it gets the xrd endpoint for statusnet" do
|
||||
{:ok, template} = WebFinger.find_lrdd_template("status.alpicola.com")
|
||||
|
||||
assert template == "http://status.alpicola.com/main/xrd?uri={uri}"
|
||||
assert template == "https://status.alpicola.com/main/xrd?uri={uri}"
|
||||
end
|
||||
|
||||
test "it works with idna domains as nickname" do
|
||||
|
@ -147,7 +147,7 @@ test "respects json content-type" do
|
|||
headers: [{"content-type", "application/jrd+json"}]
|
||||
}}
|
||||
|
||||
%{url: "http://mastodon.social/.well-known/host-meta"} ->
|
||||
%{url: "https://mastodon.social/.well-known/host-meta"} ->
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
|
@ -170,7 +170,7 @@ test "respects xml content-type" do
|
|||
headers: [{"content-type", "application/xrd+xml"}]
|
||||
}}
|
||||
|
||||
%{url: "http://pawoo.net/.well-known/host-meta"} ->
|
||||
%{url: "https://pawoo.net/.well-known/host-meta"} ->
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
|
|
|
@ -424,14 +424,6 @@ def get("http://mastodon.example.org/users/gargron", _, _, [
|
|||
{:error, :nxdomain}
|
||||
end
|
||||
|
||||
def get("http://osada.macgirvin.com/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 404,
|
||||
body: ""
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://osada.macgirvin.com/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
|
@ -765,7 +757,7 @@ def get(
|
|||
{:ok, %Tesla.Env{status: 406, body: ""}}
|
||||
end
|
||||
|
||||
def get("http://squeet.me/.well-known/host-meta", _, _, _) do
|
||||
def get("https://squeet.me/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/squeet.me_host_meta")}}
|
||||
end
|
||||
|
@ -806,7 +798,7 @@ def get(
|
|||
{:ok, %Tesla.Env{status: 200, body: "", headers: [{"content-type", "application/jrd+json"}]}}
|
||||
end
|
||||
|
||||
def get("http://framatube.org/.well-known/host-meta", _, _, _) do
|
||||
def get("https://framatube.org/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
|
@ -815,7 +807,7 @@ def get("http://framatube.org/.well-known/host-meta", _, _, _) do
|
|||
end
|
||||
|
||||
def get(
|
||||
"http://framatube.org/main/xrd?uri=acct:framasoft@framatube.org",
|
||||
"https://framatube.org/main/xrd?uri=acct:framasoft@framatube.org",
|
||||
_,
|
||||
_,
|
||||
[{"accept", "application/xrd+xml,application/jrd+json"}]
|
||||
|
@ -850,7 +842,7 @@ def get(
|
|||
}}
|
||||
end
|
||||
|
||||
def get("http://status.alpicola.com/.well-known/host-meta", _, _, _) do
|
||||
def get("https://status.alpicola.com/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
|
@ -858,7 +850,7 @@ def get("http://status.alpicola.com/.well-known/host-meta", _, _, _) do
|
|||
}}
|
||||
end
|
||||
|
||||
def get("http://macgirvin.com/.well-known/host-meta", _, _, _) do
|
||||
def get("https://macgirvin.com/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
|
@ -866,7 +858,7 @@ def get("http://macgirvin.com/.well-known/host-meta", _, _, _) do
|
|||
}}
|
||||
end
|
||||
|
||||
def get("http://gerzilla.de/.well-known/host-meta", _, _, _) do
|
||||
def get("https://gerzilla.de/.well-known/host-meta", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
|
|
Loading…
Reference in a new issue