forked from AkkomaGang/akkoma
resolve merge conflicts
This commit is contained in:
commit
a9eb94e8d3
109 changed files with 1946 additions and 374 deletions
6
.gitattributes
vendored
6
.gitattributes
vendored
|
@ -1,10 +1,4 @@
|
||||||
*.ex diff=elixir
|
*.ex diff=elixir
|
||||||
*.exs diff=elixir
|
*.exs diff=elixir
|
||||||
|
|
||||||
# Most of js/css files included in the repo are minified bundles,
|
|
||||||
# and we don't want to search/diff those as text files.
|
|
||||||
*.js binary
|
|
||||||
*.js.map binary
|
|
||||||
*.css binary
|
|
||||||
|
|
||||||
*.css diff=css
|
*.css diff=css
|
||||||
|
|
|
@ -41,7 +41,7 @@ variables:
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:13
|
image: postgres:15
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
- pull_request
|
- pull_request
|
||||||
|
|
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -9,17 +9,38 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
### Added
|
### Added
|
||||||
- Prometheus metrics exporting from `/api/v1/akkoma/metrics`
|
- Prometheus metrics exporting from `/api/v1/akkoma/metrics`
|
||||||
- Ability to alter http pool size
|
- Ability to alter http pool size
|
||||||
|
- Translation of statuses via ArgosTranslate
|
||||||
|
- Argon2 password hashing
|
||||||
|
- Ability to "verify" links in profile fields via rel=me
|
||||||
|
- Mix tasks to dump/load config to/from json for bulk editing
|
||||||
|
- Followed hashtag list at /api/v1/followed\_tags, API parity with mastodon
|
||||||
|
- Ability to set posting language in the post form, API parity with mastodon
|
||||||
|
- Ability to match domains in MRF by a trailing wildcard
|
||||||
|
- Currently supported formats:
|
||||||
|
- `example.com` (implicitly matches `*.example.com`)
|
||||||
|
- `*.example.com`
|
||||||
|
- `example.*` (implicitly matches `*.example.*`)
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- Non-finch HTTP adapters
|
- Non-finch HTTP adapters
|
||||||
- Legacy redirect from /api/pleroma/admin to /api/v1/pleroma/admin
|
- Legacy redirect from /api/pleroma/admin to /api/v1/pleroma/admin
|
||||||
- Legacy redirects from /api/pleroma to /api/v1/pleroma
|
- Legacy redirects from /api/pleroma to /api/v1/pleroma
|
||||||
|
- :crypt dependency
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Return HTTP error 413 when uploading an avatar or banner that's above the configured upload limit instead of a 500.
|
- Return HTTP error 413 when uploading an avatar or banner that's above the configured upload limit instead of a 500.
|
||||||
- Non-admin users now cannot register `admin` scope tokens (not security-critical, they didn't work before, but you _could_ create them)
|
- Non-admin users now cannot register `admin` scope tokens (not security-critical, they didn't work before, but you _could_ create them)
|
||||||
- Admin scopes will be dropped on create
|
- Admin scopes will be dropped on create
|
||||||
- Rich media will now backoff for 20 minutes after a failure
|
- Rich media will now backoff for 20 minutes after a failure
|
||||||
|
- Quote posts are now considered as part of the same thread as the post they are quoting
|
||||||
|
- Extend the mix task `prune_objects` with options to keep more relevant posts
|
||||||
|
- Simplified HTTP signature processing
|
||||||
|
- Rich media will now hard-exit after 5 seconds, to prevent timeline hangs
|
||||||
|
- HTTP Content Security Policy is now far more strict to prevent any potential XSS/CSS leakages
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- /api/v1/accounts/lookup will now respect restrict\_unauthenticated
|
||||||
|
- Unknown atoms in the config DB will no longer crash akkoma on boot
|
||||||
|
|
||||||
### Upgrade notes
|
### Upgrade notes
|
||||||
- Ensure `config :tesla, :adapter` is either unset, or set to `{Tesla.Adapter.Finch, name: MyFinch}` in your .exs config
|
- Ensure `config :tesla, :adapter` is either unset, or set to `{Tesla.Adapter.Finch, name: MyFinch}` in your .exs config
|
||||||
|
|
|
@ -882,6 +882,11 @@
|
||||||
url: "http://127.0.0.1:5000",
|
url: "http://127.0.0.1:5000",
|
||||||
api_key: nil
|
api_key: nil
|
||||||
|
|
||||||
|
config :pleroma, :argos_translate,
|
||||||
|
command_argos_translate: "argos-translate",
|
||||||
|
command_argospm: "argospm",
|
||||||
|
strip_html: true
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
# of this file so it overrides the configuration defined above.
|
# of this file so it overrides the configuration defined above.
|
||||||
import_config "#{Mix.env()}.exs"
|
import_config "#{Mix.env()}.exs"
|
||||||
|
|
|
@ -3442,5 +3442,32 @@
|
||||||
suggestion: [nil]
|
suggestion: [nil]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
group: :pleroma,
|
||||||
|
key: :argos_translate,
|
||||||
|
type: :group,
|
||||||
|
description: "ArgosTranslate Settings.",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :command_argos_translate,
|
||||||
|
type: :string,
|
||||||
|
description:
|
||||||
|
"command for `argos-translate`. Can be the command if it's in your PATH, or the full path to the file.",
|
||||||
|
suggestion: ["argos-translate"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :command_argospm,
|
||||||
|
type: :string,
|
||||||
|
description:
|
||||||
|
"command for `argospm`. Can be the command if it's in your PATH, or the full path to the file.",
|
||||||
|
suggestion: ["argospm"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :strip_html,
|
||||||
|
type: :boolean,
|
||||||
|
description: "Strip html from the post before translating it."
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -155,3 +155,51 @@ This forcibly removes all saved values in the database.
|
||||||
```sh
|
```sh
|
||||||
mix pleroma.config [--force] reset
|
mix pleroma.config [--force] reset
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Dumping specific configuration values to JSON
|
||||||
|
|
||||||
|
If you want to bulk-modify configuration values (for example, for MRF modifications),
|
||||||
|
it may be easier to dump the values to JSON and then modify them in a text editor.
|
||||||
|
|
||||||
|
=== "OTP"
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./bin/pleroma_ctl config dump_to_file group key path
|
||||||
|
# For example, to dump the MRF simple configuration:
|
||||||
|
./bin/pleroma_ctl config dump_to_file pleroma mrf_simple /tmp/mrf_simple.json
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "From Source"
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mix pleroma.config dump_to_file group key path
|
||||||
|
# For example, to dump the MRF simple configuration:
|
||||||
|
mix pleroma.config dump_to_file pleroma mrf_simple /tmp/mrf_simple.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Loading specific configuration values from JSON
|
||||||
|
|
||||||
|
**Note:** This will overwrite any existing value in the database, and can
|
||||||
|
cause crashes if you do not have exactly the correct formatting.
|
||||||
|
|
||||||
|
Once you have modified the JSON file, you can load it back into the database.
|
||||||
|
|
||||||
|
=== "OTP"
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./bin/pleroma_ctl config load_from_file path
|
||||||
|
# For example, to load the MRF simple configuration:
|
||||||
|
./bin/pleroma_ctl config load_from_file /tmp/mrf_simple.json
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "From Source"
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mix pleroma.config load_from_file path
|
||||||
|
# For example, to load the MRF simple configuration:
|
||||||
|
mix pleroma.config load_from_file /tmp/mrf_simple.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**NOTE** an instance reboot is needed for many changes to take effect,
|
||||||
|
you may want to visit `/api/v1/pleroma/admin/restart` on your instance
|
||||||
|
to soft-restart the instance.
|
||||||
|
|
|
@ -27,7 +27,7 @@ Replaces embedded objects with references to them in the `objects` table. Only n
|
||||||
|
|
||||||
## Prune old remote posts from the database
|
## Prune old remote posts from the database
|
||||||
|
|
||||||
This will prune remote posts older than 90 days (configurable with [`config :pleroma, :instance, remote_post_retention_days`](../../configuration/cheatsheet.md#instance)) from the database, they will be refetched from source when accessed.
|
This will prune remote posts older than 90 days (configurable with [`config :pleroma, :instance, remote_post_retention_days`](../../configuration/cheatsheet.md#instance)) from the database. Pruned posts may be refetched in some cases.
|
||||||
|
|
||||||
!!! danger
|
!!! danger
|
||||||
The disk space will only be reclaimed after `VACUUM FULL`. You may run out of disk space during the execution of the task or vacuuming if you don't have about 1/3rds of the database size free.
|
The disk space will only be reclaimed after `VACUUM FULL`. You may run out of disk space during the execution of the task or vacuuming if you don't have about 1/3rds of the database size free.
|
||||||
|
@ -45,6 +45,9 @@ This will prune remote posts older than 90 days (configurable with [`config :ple
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
|
- `--keep-threads` - don't prune posts when they are part of a thread where at least one post has seen local interaction (e.g. one of the posts is a local post, or is favourited by a local user, or has been repeated by a local user...)
|
||||||
|
- `--keep-non-public` - keep non-public posts like DM's and followers-only, even if they are remote
|
||||||
- `--vacuum` - run `VACUUM FULL` after the objects are pruned
|
- `--vacuum` - run `VACUUM FULL` after the objects are pruned
|
||||||
|
|
||||||
## Create a conversation for all existing DMs
|
## Create a conversation for all existing DMs
|
||||||
|
@ -178,4 +181,4 @@ to the current day.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
mix pleroma.database prune_task
|
mix pleroma.database prune_task
|
||||||
```
|
```
|
||||||
|
|
|
@ -24,20 +24,20 @@ Currently, known `<frontend>` values are:
|
||||||
|
|
||||||
You can still install frontends that are not configured, see below.
|
You can still install frontends that are not configured, see below.
|
||||||
|
|
||||||
## Example installations for a known frontend
|
## Example installations for a known frontend (Stable-Version)
|
||||||
|
|
||||||
For a frontend configured under the `available` key, it's enough to install it by name.
|
For a frontend configured under the `available` key, it's enough to install it by name.
|
||||||
|
|
||||||
=== "OTP"
|
=== "OTP"
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./bin/pleroma_ctl frontend install pleroma-fe
|
./bin/pleroma_ctl frontend install pleroma-fe --ref stable
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "From Source"
|
=== "From Source"
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
mix pleroma.frontend install pleroma-fe
|
mix pleroma.frontend install pleroma-fe --ref stable
|
||||||
```
|
```
|
||||||
|
|
||||||
This will download the latest build for the pre-configured `ref` and install it. It can then be configured as the one of the served frontends in the config file (see `primary` or `admin`).
|
This will download the latest build for the pre-configured `ref` and install it. It can then be configured as the one of the served frontends in the config file (see `primary` or `admin`).
|
||||||
|
|
|
@ -11,11 +11,11 @@ If you want to generate a restrictive `robots.txt`, you can run the following mi
|
||||||
=== "OTP"
|
=== "OTP"
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./bin/pleroma_ctl robots_txt disallow_all
|
./bin/pleroma_ctl robotstxt disallow_all
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "From Source"
|
=== "From Source"
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
mix pleroma.robots_txt disallow_all
|
mix pleroma.robotstxt disallow_all
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Updating your instance
|
# Updating your instance
|
||||||
|
|
||||||
You should **always check the [release notes/changelog](https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/CHANGELOG.md)** in case there are config deprecations, special update steps, etc.
|
You should **always check the [release notes/changelog](https://akkoma.dev/AkkomaGang/akkoma/src/branch/stable/CHANGELOG.md)** in case there are config deprecations, special update steps, etc.
|
||||||
|
|
||||||
Besides that, doing the following is generally enough:
|
Besides that, doing the following is generally enough:
|
||||||
## Switch to the akkoma user
|
## Switch to the akkoma user
|
||||||
|
@ -41,8 +41,10 @@ you _may_ need to specify `--flavour`, in the same way as
|
||||||
Run as the `akkoma` user:
|
Run as the `akkoma` user:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Pull in new changes
|
# fetch changes
|
||||||
git pull
|
git fetch
|
||||||
|
# check out the latest tag
|
||||||
|
git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)
|
||||||
|
|
||||||
# Run with production configuration
|
# Run with production configuration
|
||||||
export MIX_ENV=prod
|
export MIX_ENV=prod
|
||||||
|
@ -57,7 +59,7 @@ sudo systemctl stop akkoma
|
||||||
# Run database migrations
|
# Run database migrations
|
||||||
mix ecto.migrate
|
mix ecto.migrate
|
||||||
|
|
||||||
# Update frontend(s). See Frontend Configration doc for more information.
|
# Update Pleroma-FE frontend to latest stable. For other Frontends see Frontend Configration doc for more information.
|
||||||
mix pleroma.frontend install pleroma-fe --ref stable
|
mix pleroma.frontend install pleroma-fe --ref stable
|
||||||
|
|
||||||
# Start akkoma (replace with your system service manager's equivalent if different)
|
# Start akkoma (replace with your system service manager's equivalent if different)
|
||||||
|
|
|
@ -1119,7 +1119,7 @@ Each job has these settings:
|
||||||
### Translation Settings
|
### Translation Settings
|
||||||
|
|
||||||
Settings to automatically translate statuses for end users. Currently supported
|
Settings to automatically translate statuses for end users. Currently supported
|
||||||
translation services are DeepL and LibreTranslate.
|
translation services are DeepL and LibreTranslate. The supported command line tool is [Argos Translate](https://github.com/argosopentech/argos-translate).
|
||||||
|
|
||||||
Translations are available at `/api/v1/statuses/:id/translations/:language`, where
|
Translations are available at `/api/v1/statuses/:id/translations/:language`, where
|
||||||
`language` is the target language code (e.g `en`)
|
`language` is the target language code (e.g `en`)
|
||||||
|
@ -1128,7 +1128,7 @@ Translations are available at `/api/v1/statuses/:id/translations/:language`, whe
|
||||||
|
|
||||||
- `:enabled` - enables translation
|
- `:enabled` - enables translation
|
||||||
- `:module` - Sets module to be used
|
- `:module` - Sets module to be used
|
||||||
- Either `Pleroma.Akkoma.Translators.DeepL` or `Pleroma.Akkoma.Translators.LibreTranslate`
|
- Either `Pleroma.Akkoma.Translators.DeepL`, `Pleroma.Akkoma.Translators.LibreTranslate`, or `Pleroma.Akkoma.Translators.ArgosTranslate`
|
||||||
|
|
||||||
### `:deepl`
|
### `:deepl`
|
||||||
|
|
||||||
|
@ -1140,3 +1140,9 @@ Translations are available at `/api/v1/statuses/:id/translations/:language`, whe
|
||||||
|
|
||||||
- `:url` - URL of LibreTranslate instance
|
- `:url` - URL of LibreTranslate instance
|
||||||
- `:api_key` - API key for LibreTranslate
|
- `:api_key` - API key for LibreTranslate
|
||||||
|
|
||||||
|
### `:argos_translate`
|
||||||
|
|
||||||
|
- `:command_argos_translate` - command for `argos-translate`. Can be the command if it's in your PATH, or the full path to the file (default: `argos-translate`).
|
||||||
|
- `:command_argospm` - command for `argospm`. Can be the command if it's in your PATH, or the full path to the file (default: `argospm`).
|
||||||
|
- `:strip_html` - Strip html from the post before translating it (default: `true`).
|
||||||
|
|
|
@ -67,3 +67,29 @@ Priority of tags assigns in emoji.txt and custom.txt:
|
||||||
Priority for globs:
|
Priority for globs:
|
||||||
|
|
||||||
`special group setting in config.exs > default setting in config.exs`
|
`special group setting in config.exs > default setting in config.exs`
|
||||||
|
|
||||||
|
## Stealing emoji
|
||||||
|
|
||||||
|
Managing your emoji can be hard work, and you just want to have the cool emoji your friends use? As usual, crime comes to the rescue!
|
||||||
|
|
||||||
|
You can use the `Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy` [Message Rewrite Facility](../configuration/cheatsheet.md#mrf) to automatically add to your instance emoji that messages from specific servers contain. Note that this happens on message processing, so the emoji will be added only after your instance receives some interaction containing emoji _after_ configuring this.
|
||||||
|
|
||||||
|
To activate this you have to [configure](../configuration/cheatsheet.md#mrf_steal_emoji) it in your configuration file. For example if you wanted to steal any emoji that is not related to cinnamon and not larger than about 10K from `coolemoji.space` and `spiceenthusiasts.biz`, you would add the following:
|
||||||
|
```elixir
|
||||||
|
config :pleroma, :mrf,
|
||||||
|
policies: [
|
||||||
|
Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy
|
||||||
|
]
|
||||||
|
|
||||||
|
config :pleroma, :mrf_steal_emoji,
|
||||||
|
hosts: [
|
||||||
|
"coolemoji.space",
|
||||||
|
"spiceenthusiasts.biz"
|
||||||
|
],
|
||||||
|
rejected_shortcodes: [
|
||||||
|
".*cinnamon.*"
|
||||||
|
],
|
||||||
|
size_limit: 10000
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that this may not obey emoji licensing restrictions. It's extremely unlikely that anyone will care, but keep this in mind for when Nintendo starts their own instance.
|
||||||
|
|
|
@ -84,12 +84,12 @@ doas adduser -S -s /bin/false -h /opt/akkoma -H -G akkoma akkoma
|
||||||
|
|
||||||
**Note**: To execute a single command as the Akkoma system user, use `doas -u akkoma command`. You can also switch to a shell by using `doas -su akkoma`. If you don’t have and want `doas` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l akkoma -s $SHELL -c 'command'` and `su -l akkoma -s $SHELL` for starting a shell.
|
**Note**: To execute a single command as the Akkoma system user, use `doas -u akkoma command`. You can also switch to a shell by using `doas -su akkoma`. If you don’t have and want `doas` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l akkoma -s $SHELL -c 'command'` and `su -l akkoma -s $SHELL` for starting a shell.
|
||||||
|
|
||||||
* Git clone the AkkomaBE repository and make the Akkoma user the owner of the directory:
|
* Git clone the AkkomaBE repository from stable-branch and make the Akkoma user the owner of the directory:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
doas mkdir -p /opt/akkoma
|
doas mkdir -p /opt/akkoma
|
||||||
doas chown -R akkoma:akkoma /opt/akkoma
|
doas chown -R akkoma:akkoma /opt/akkoma
|
||||||
doas -u akkoma git clone https://akkoma.dev/AkkomaGang/akkoma.git /opt/akkoma
|
doas -u akkoma git clone https://akkoma.dev/AkkomaGang/akkoma.git -b stable /opt/akkoma
|
||||||
```
|
```
|
||||||
|
|
||||||
* Change to the new directory:
|
* Change to the new directory:
|
||||||
|
@ -109,7 +109,7 @@ doas -u akkoma mix deps.get
|
||||||
* This may take some time, because parts of akkoma get compiled first.
|
* This may take some time, because parts of akkoma get compiled first.
|
||||||
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
||||||
|
|
||||||
* Check the configuration and if all looks right, rename it, so Akkoma will load it (`prod.secret.exs` for productive instance, `dev.secret.exs` for development instances):
|
* Check the configuration and if all looks right, rename it, so Akkoma will load it (`prod.secret.exs` for productive instances):
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
doas -u akkoma mv config/{generated_config.exs,prod.secret.exs}
|
doas -u akkoma mv config/{generated_config.exs,prod.secret.exs}
|
||||||
|
|
|
@ -75,12 +75,12 @@ sudo useradd -r -s /bin/false -m -d /var/lib/akkoma -U akkoma
|
||||||
|
|
||||||
**Note**: To execute a single command as the Akkoma system user, use `sudo -Hu akkoma command`. You can also switch to a shell by using `sudo -Hu akkoma $SHELL`. If you don’t have and want `sudo` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l akkoma -s $SHELL -c 'command'` and `su -l akkoma -s $SHELL` for starting a shell.
|
**Note**: To execute a single command as the Akkoma system user, use `sudo -Hu akkoma command`. You can also switch to a shell by using `sudo -Hu akkoma $SHELL`. If you don’t have and want `sudo` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l akkoma -s $SHELL -c 'command'` and `su -l akkoma -s $SHELL` for starting a shell.
|
||||||
|
|
||||||
* Git clone the AkkomaBE repository and make the Akkoma user the owner of the directory:
|
* Git clone the AkkomaBE repository from stable-branch and make the Akkoma user the owner of the directory:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo mkdir -p /opt/akkoma
|
sudo mkdir -p /opt/akkoma
|
||||||
sudo chown -R akkoma:akkoma /opt/akkoma
|
sudo chown -R akkoma:akkoma /opt/akkoma
|
||||||
sudo -Hu akkoma git clone https://akkoma.dev/AkkomaGang/akkoma.git /opt/akkoma
|
sudo -Hu akkoma git clone https://akkoma.dev/AkkomaGang/akkoma.git -b stable /opt/akkoma
|
||||||
```
|
```
|
||||||
|
|
||||||
* Change to the new directory:
|
* Change to the new directory:
|
||||||
|
@ -100,7 +100,7 @@ sudo -Hu akkoma mix deps.get
|
||||||
* This may take some time, because parts of akkoma get compiled first.
|
* This may take some time, because parts of akkoma get compiled first.
|
||||||
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
||||||
|
|
||||||
* Check the configuration and if all looks right, rename it, so Akkoma will load it (`prod.secret.exs` for productive instance, `dev.secret.exs` for development instances):
|
* Check the configuration and if all looks right, rename it, so Akkoma will load it (`prod.secret.exs` for productive instances):
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo -Hu akkoma mv config/{generated_config.exs,prod.secret.exs}
|
sudo -Hu akkoma mv config/{generated_config.exs,prod.secret.exs}
|
||||||
|
|
|
@ -49,12 +49,12 @@ sudo useradd -r -s /bin/false -m -d /var/lib/akkoma -U akkoma
|
||||||
|
|
||||||
**Note**: To execute a single command as the Akkoma system user, use `sudo -Hu akkoma command`. You can also switch to a shell by using `sudo -Hu akkoma $SHELL`. If you don’t have and want `sudo` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l akkoma -s $SHELL -c 'command'` and `su -l akkoma -s $SHELL` for starting a shell.
|
**Note**: To execute a single command as the Akkoma system user, use `sudo -Hu akkoma command`. You can also switch to a shell by using `sudo -Hu akkoma $SHELL`. If you don’t have and want `sudo` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l akkoma -s $SHELL -c 'command'` and `su -l akkoma -s $SHELL` for starting a shell.
|
||||||
|
|
||||||
* Git clone the AkkomaBE repository and make the Akkoma user the owner of the directory:
|
* Git clone the AkkomaBE repository from stable-branch and make the Akkoma user the owner of the directory:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo mkdir -p /opt/akkoma
|
sudo mkdir -p /opt/akkoma
|
||||||
sudo chown -R akkoma:akkoma /opt/akkoma
|
sudo chown -R akkoma:akkoma /opt/akkoma
|
||||||
sudo -Hu akkoma git clone https://akkoma.dev/AkkomaGang/akkoma.git /opt/akkoma
|
sudo -Hu akkoma git clone https://akkoma.dev/AkkomaGang/akkoma.git -b stable /opt/akkoma
|
||||||
```
|
```
|
||||||
|
|
||||||
* Change to the new directory:
|
* Change to the new directory:
|
||||||
|
@ -74,7 +74,7 @@ sudo -Hu akkoma mix deps.get
|
||||||
* This may take some time, because parts of akkoma get compiled first.
|
* This may take some time, because parts of akkoma get compiled first.
|
||||||
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
||||||
|
|
||||||
* Check the configuration and if all looks right, rename it, so Akkoma will load it (`prod.secret.exs` for productive instance, `dev.secret.exs` for development instances):
|
* Check the configuration and if all looks right, rename it, so Akkoma will load it (`prod.secret.exs` for productive instances):
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo -Hu akkoma mv config/{generated_config.exs,prod.secret.exs}
|
sudo -Hu akkoma mv config/{generated_config.exs,prod.secret.exs}
|
||||||
|
|
|
@ -30,11 +30,10 @@ sudo dnf install git gcc g++ make cmake file-devel postgresql-server postgresql-
|
||||||
|
|
||||||
* Enable and initialize Postgres:
|
* Enable and initialize Postgres:
|
||||||
```shell
|
```shell
|
||||||
sudo systemctl enable postgresql.service
|
|
||||||
sudo postgresql-setup --initdb --unit postgresql
|
sudo postgresql-setup --initdb --unit postgresql
|
||||||
# Allow password auth for postgres
|
# Allow password auth for postgres
|
||||||
sudo sed -E -i 's|(host +all +all +127.0.0.1/32 +)ident|\1md5|' /var/lib/pgsql/data/pg_hba.conf
|
sudo sed -E -i 's|(host +all +all +127.0.0.1/32 +)ident|\1md5|' /var/lib/pgsql/data/pg_hba.conf
|
||||||
sudo systemctl start postgresql.service
|
sudo systemctl enable --now postgresql.service
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install Elixir and Erlang
|
### Install Elixir and Erlang
|
||||||
|
@ -59,7 +58,7 @@ sudo dnf install ffmpeg
|
||||||
* Install ImageMagick and ExifTool for image manipulation:
|
* Install ImageMagick and ExifTool for image manipulation:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo dnf install Imagemagick perl-Image-ExifTool
|
sudo dnf install ImageMagick perl-Image-ExifTool
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,12 +73,12 @@ sudo useradd -r -s /bin/false -m -d /var/lib/akkoma -U akkoma
|
||||||
|
|
||||||
**Note**: To execute a single command as the Akkoma system user, use `sudo -Hu akkoma command`. You can also switch to a shell by using `sudo -Hu akkoma $SHELL`. If you don’t have and want `sudo` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l akkoma -s $SHELL -c 'command'` and `su -l akkoma -s $SHELL` for starting a shell.
|
**Note**: To execute a single command as the Akkoma system user, use `sudo -Hu akkoma command`. You can also switch to a shell by using `sudo -Hu akkoma $SHELL`. If you don’t have and want `sudo` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l akkoma -s $SHELL -c 'command'` and `su -l akkoma -s $SHELL` for starting a shell.
|
||||||
|
|
||||||
* Git clone the AkkomaBE repository and make the Akkoma user the owner of the directory:
|
* Git clone the AkkomaBE repository from stable-branch and make the Akkoma user the owner of the directory:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo mkdir -p /opt/akkoma
|
sudo mkdir -p /opt/akkoma
|
||||||
sudo chown -R akkoma:akkoma /opt/akkoma
|
sudo chown -R akkoma:akkoma /opt/akkoma
|
||||||
sudo -Hu akkoma git clone https://akkoma.dev/AkkomaGang/akkoma.git /opt/akkoma
|
sudo -Hu akkoma git clone https://akkoma.dev/AkkomaGang/akkoma.git -b stable /opt/akkoma
|
||||||
```
|
```
|
||||||
|
|
||||||
* Change to the new directory:
|
* Change to the new directory:
|
||||||
|
@ -99,7 +98,7 @@ sudo -Hu akkoma mix deps.get
|
||||||
* This may take some time, because parts of akkoma get compiled first.
|
* This may take some time, because parts of akkoma get compiled first.
|
||||||
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
||||||
|
|
||||||
* Check the configuration and if all looks right, rename it, so Akkoma will load it (`prod.secret.exs` for productive instance, `dev.secret.exs` for development instances):
|
* Check the configuration and if all looks right, rename it, so Akkoma will load it (`prod.secret.exs` for productive instances):
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo -Hu akkoma mv config/{generated_config.exs,prod.secret.exs}
|
sudo -Hu akkoma mv config/{generated_config.exs,prod.secret.exs}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Installing on OpenBSD
|
# Installing on OpenBSD
|
||||||
|
|
||||||
This guide describes the installation and configuration of akkoma (and the required software to run it) on a single OpenBSD 6.6 server.
|
This guide describes the installation and configuration of akkoma (and the required software to run it) on a single OpenBSD 7.2 server.
|
||||||
|
|
||||||
For any additional information regarding commands and configuration files mentioned here, check the man pages [online](https://man.openbsd.org/) or directly on your server with the man command.
|
For any additional information regarding commands and configuration files mentioned here, check the man pages [online](https://man.openbsd.org/) or directly on your server with the man command.
|
||||||
|
|
||||||
|
@ -12,11 +12,10 @@ For any additional information regarding commands and configuration files mentio
|
||||||
To install them, run the following command (with doas or as root):
|
To install them, run the following command (with doas or as root):
|
||||||
|
|
||||||
```
|
```
|
||||||
pkg_add elixir gmake git postgresql-server postgresql-contrib cmake ffmpeg ImageMagick erlang-wx-25
|
pkg_add elixir gmake git postgresql-server postgresql-contrib cmake ffmpeg erlang-wx libmagic
|
||||||
|
pkg_add erlang-wx # Choose the latest version as package version when promted
|
||||||
```
|
```
|
||||||
|
|
||||||
(Note that the erlang version may change, it was 25 at the time of writing)
|
|
||||||
|
|
||||||
Akkoma requires a reverse proxy, OpenBSD has relayd in base (and is used in this guide) and packages/ports are available for nginx (www/nginx) and apache (www/apache-httpd). Independently of the reverse proxy, [acme-client(1)](https://man.openbsd.org/acme-client) can be used to get a certificate from Let's Encrypt.
|
Akkoma requires a reverse proxy, OpenBSD has relayd in base (and is used in this guide) and packages/ports are available for nginx (www/nginx) and apache (www/apache-httpd). Independently of the reverse proxy, [acme-client(1)](https://man.openbsd.org/acme-client) can be used to get a certificate from Let's Encrypt.
|
||||||
|
|
||||||
#### Optional software
|
#### Optional software
|
||||||
|
@ -29,32 +28,35 @@ Per [`docs/installation/optional/media_graphics_packages.md`](../installation/op
|
||||||
To install the above:
|
To install the above:
|
||||||
|
|
||||||
```
|
```
|
||||||
pkg_add ImageMagick ffmpeg p5-Image-ExifTool
|
pkg_add ffmpeg p5-Image-ExifTool
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Creating the akkoma user
|
#### Creating the akkoma user
|
||||||
Akkoma will be run by a dedicated user, \_akkoma. Before creating it, insert the following lines in login.conf:
|
Akkoma will be run by a dedicated user, `_akkoma`. Before creating it, insert the following lines in `/etc/login.conf`:
|
||||||
```
|
```
|
||||||
akkoma:\
|
akkoma:\
|
||||||
:datasize-max=1536M:\
|
:datasize-max=1536M:\
|
||||||
:datasize-cur=1536M:\
|
:datasize-cur=1536M:\
|
||||||
:openfiles-max=4096
|
:openfiles-max=4096
|
||||||
```
|
```
|
||||||
This creates a "akkoma" login class and sets higher values than default for datasize and openfiles (see [login.conf(5)](https://man.openbsd.org/login.conf)), this is required to avoid having akkoma crash some time after starting.
|
This creates a `akkoma` login class and sets higher values than default for datasize and openfiles (see [login.conf(5)](https://man.openbsd.org/login.conf)), this is required to avoid having akkoma crash some time after starting.
|
||||||
|
|
||||||
Create the \_akkoma user, assign it the akkoma login class and create its home directory (/home/\_akkoma/): `useradd -m -L akkoma _akkoma`
|
Create the `_akkoma` user, assign it the akkoma login class and create its home directory (`/home/_akkoma/`): `useradd -m -L akkoma _akkoma`
|
||||||
|
|
||||||
#### Clone akkoma's directory
|
#### Clone akkoma's directory
|
||||||
Enter a shell as the \_akkoma user. As root, run `su _akkoma -;cd`. Then clone the repository with `git clone https://akkoma.dev/AkkomaGang/akkoma.git`. Akkoma is now installed in /home/\_akkoma/akkoma/, it will be configured and started at the end of this guide.
|
Enter a shell as the `_akkoma` user. As root, run `su _akkoma -;cd`. Then clone the repository with `git clone https://akkoma.dev/AkkomaGang/akkoma.git`. Akkoma is now installed in `/home/_akkoma/akkoma/`, it will be configured and started at the end of this guide.
|
||||||
|
|
||||||
#### PostgreSQL
|
#### PostgreSQL
|
||||||
Start a shell as the \_postgresql user (as root run `su _postgresql -` then run the `initdb` command to initialize postgresql:
|
Create `_postgresql`'s user directory (it hasn't been created yet): `mdir var/postgresql/data`. To set it as home
|
||||||
You will need to specify pgdata directory to the default (/var/postgresql/data) with the `-D <path>` and set the user to postgres with the `-U <username>` flag. This can be done as follows:
|
directory for user `_postgresql` run `usermod -d /var/postgresql/data _postgresql`.
|
||||||
|
|
||||||
|
Start a shell as the `_postgresql` user (as root run `su _postgresql -` then run the `initdb` command to initialize postgresql.
|
||||||
|
You will need to specify pgdata directory to the default (`/var/postgresql/data`) with the `-D <path>` and set the user to postgres with the `-U <username>` flag. This can be done as follows:
|
||||||
|
|
||||||
```
|
```
|
||||||
initdb -D /var/postgresql/data -U postgres
|
initdb -D /var/postgresql/data -U postgres
|
||||||
```
|
```
|
||||||
If you are not using the default directory, you will have to update the `datadir` variable in the /etc/rc.d/postgresql script.
|
If you are not using the default directory, you will have to update the `datadir` variable in the `/etc/rc.d/postgresql` script.
|
||||||
|
|
||||||
When this is done, enable postgresql so that it starts on boot and start it. As root, run:
|
When this is done, enable postgresql so that it starts on boot and start it. As root, run:
|
||||||
```
|
```
|
||||||
|
@ -70,7 +72,7 @@ httpd will have three fuctions:
|
||||||
* serve a robots.txt file
|
* serve a robots.txt file
|
||||||
* get Let's Encrypt certificates, with acme-client
|
* get Let's Encrypt certificates, with acme-client
|
||||||
|
|
||||||
Insert the following config in httpd.conf:
|
Insert the following config in `/etc/httpd.conf`:
|
||||||
```
|
```
|
||||||
# $OpenBSD: httpd.conf,v 1.17 2017/04/16 08:50:49 ajacoutot Exp $
|
# $OpenBSD: httpd.conf,v 1.17 2017/04/16 08:50:49 ajacoutot Exp $
|
||||||
|
|
||||||
|
@ -93,13 +95,10 @@ server "default" {
|
||||||
location "/robots.txt" { root "/htdocs/local/" }
|
location "/robots.txt" { root "/htdocs/local/" }
|
||||||
location "/*" { block return 302 "https://$HTTP_HOST$REQUEST_URI" }
|
location "/*" { block return 302 "https://$HTTP_HOST$REQUEST_URI" }
|
||||||
}
|
}
|
||||||
|
|
||||||
types {
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
Do not forget to change *<IPv4/6 address\>* to your server's address(es). If httpd should only listen on one protocol family, comment one of the two first *listen* options.
|
Do not forget to change *<IPv4/6 address\>* to your server's address(es). If httpd should only listen on one protocol family, comment one of the two first *listen* options.
|
||||||
|
|
||||||
Create the /var/www/htdocs/local/ folder and write the content of your robots.txt in /var/www/htdocs/local/robots.txt.
|
Create the `/var/www/htdocs/local/` folder and write the content of your robots.txt in `/var/www/htdocs/local/robots.txt`.
|
||||||
Check the configuration with `httpd -n`, if it is OK enable and start httpd (as root):
|
Check the configuration with `httpd -n`, if it is OK enable and start httpd (as root):
|
||||||
```
|
```
|
||||||
rcctl enable httpd
|
rcctl enable httpd
|
||||||
|
@ -108,7 +107,7 @@ rcctl start httpd
|
||||||
|
|
||||||
#### acme-client
|
#### acme-client
|
||||||
acme-client is used to get SSL/TLS certificates from Let's Encrypt.
|
acme-client is used to get SSL/TLS certificates from Let's Encrypt.
|
||||||
Insert the following configuration in /etc/acme-client.conf:
|
Insert the following configuration in `/etc/acme-client.conf`:
|
||||||
```
|
```
|
||||||
#
|
#
|
||||||
# $OpenBSD: acme-client.conf,v 1.4 2017/03/22 11:14:14 benno Exp $
|
# $OpenBSD: acme-client.conf,v 1.4 2017/03/22 11:14:14 benno Exp $
|
||||||
|
@ -129,7 +128,7 @@ domain <domain name> {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Replace *<domain name\>* by the domain name you'll use for your instance. As root, run `acme-client -n` to check the config, then `acme-client -ADv <domain name>` to create account and domain keys, and request a certificate for the first time.
|
Replace *<domain name\>* by the domain name you'll use for your instance. As root, run `acme-client -n` to check the config, then `acme-client -ADv <domain name>` to create account and domain keys, and request a certificate for the first time.
|
||||||
Make acme-client run everyday by adding it in /etc/daily.local. As root, run the following command: `echo "acme-client <domain name>" >> /etc/daily.local`.
|
Make acme-client run everyday by adding it in `/etc/daily.local`. As root, run the following command: `echo "acme-client <domain name>" >> /etc/daily.local`.
|
||||||
|
|
||||||
Relayd will look for certificates and keys based on the address it listens on (see next part), the easiest way to make them available to relayd is to create a link, as root run:
|
Relayd will look for certificates and keys based on the address it listens on (see next part), the easiest way to make them available to relayd is to create a link, as root run:
|
||||||
```
|
```
|
||||||
|
@ -140,7 +139,7 @@ This will have to be done for each IPv4 and IPv6 address relayd listens on.
|
||||||
|
|
||||||
#### relayd
|
#### relayd
|
||||||
relayd will be used as the reverse proxy sitting in front of akkoma.
|
relayd will be used as the reverse proxy sitting in front of akkoma.
|
||||||
Insert the following configuration in /etc/relayd.conf:
|
Insert the following configuration in `/etc/relayd.conf`:
|
||||||
```
|
```
|
||||||
# $OpenBSD: relayd.conf,v 1.4 2018/03/23 09:55:06 claudio Exp $
|
# $OpenBSD: relayd.conf,v 1.4 2018/03/23 09:55:06 claudio Exp $
|
||||||
|
|
||||||
|
@ -198,7 +197,7 @@ rcctl start relayd
|
||||||
|
|
||||||
#### pf
|
#### pf
|
||||||
Enabling and configuring pf is highly recommended.
|
Enabling and configuring pf is highly recommended.
|
||||||
In /etc/pf.conf, insert the following configuration:
|
In `/etc/pf.conf`, insert the following configuration:
|
||||||
```
|
```
|
||||||
# Macros
|
# Macros
|
||||||
if="<network interface>"
|
if="<network interface>"
|
||||||
|
@ -222,31 +221,30 @@ pass in quick on $if inet6 proto icmp6 to ($if) icmp6-type { echoreq unreach par
|
||||||
pass in quick on $if proto tcp to ($if) port { http https } # relayd/httpd
|
pass in quick on $if proto tcp to ($if) port { http https } # relayd/httpd
|
||||||
pass in quick on $if proto tcp from $authorized_ssh_clients to ($if) port ssh
|
pass in quick on $if proto tcp from $authorized_ssh_clients to ($if) port ssh
|
||||||
```
|
```
|
||||||
Replace *<network interface\>* by your server's network interface name (which you can get with ifconfig). Consider replacing the content of the authorized\_ssh\_clients macro by, for exemple, your home IP address, to avoid SSH connection attempts from bots.
|
Replace *<network interface\>* by your server's network interface name (which you can get with ifconfig). Consider replacing the content of the `authorized_ssh_clients` macro by, for example, your home IP address, to avoid SSH connection attempts from bots.
|
||||||
|
|
||||||
Check pf's configuration by running `pfctl -nf /etc/pf.conf`, load it with `pfctl -f /etc/pf.conf` and enable pf at boot with `rcctl enable pf`.
|
Check pf's configuration by running `pfctl -nf /etc/pf.conf`, load it with `pfctl -f /etc/pf.conf` and enable pf at boot with `rcctl enable pf`.
|
||||||
|
|
||||||
#### Configure and start akkoma
|
#### Configure and start akkoma
|
||||||
Enter a shell as \_akkoma (as root `su _akkoma -`) and enter akkoma's installation directory (`cd ~/akkoma/`).
|
Enter a shell as `_akkoma` (as root `su _akkoma -`) and enter akkoma's installation directory (`cd ~/akkoma/`).
|
||||||
|
|
||||||
Then follow the main installation guide:
|
Then follow the main installation guide:
|
||||||
|
|
||||||
* run `mix deps.get`
|
* run `mix deps.get`
|
||||||
* run `MIX_ENV=prod mix pleroma.instance gen` and enter your instance's information when asked
|
* run `MIX_ENV=prod mix pleroma.instance gen` and enter your instance's information when asked
|
||||||
* copy config/generated\_config.exs to config/prod.secret.exs. The default values should be sufficient but you should edit it and check that everything seems OK.
|
* copy `config/generated_config.exs` to `config/prod.secret.exs`. The default values should be sufficient but you should edit it and check that everything seems OK.
|
||||||
* exit your current shell back to a root one and run `psql -U postgres -f /home/_akkoma/akkoma/config/setup_db.psql` to setup the database.
|
* exit your current shell back to a root one and run `psql -U postgres -f /home/_akkoma/akkoma/config/setup_db.psql` to setup the database.
|
||||||
* return to a \_akkoma shell into akkoma's installation directory (`su _akkoma -;cd ~/akkoma`) and run `MIX_ENV=prod mix ecto.migrate`
|
* return to a `_akkoma` shell into akkoma's installation directory (`su _akkoma -;cd ~/akkoma`) and run `MIX_ENV=prod mix ecto.migrate`
|
||||||
|
|
||||||
As \_akkoma in /home/\_akkoma/akkoma, you can now run `LC_ALL=en_US.UTF-8 MIX_ENV=prod mix phx.server` to start your instance.
|
As `_akkoma` in `/home/_akkoma/akkoma`, you can now run `LC_ALL=en_US.UTF-8 MIX_ENV=prod mix phx.server` to start your instance.
|
||||||
In another SSH session/tmux window, check that it is working properly by running `ftp -MVo - http://127.0.0.1:4000/api/v1/instance`, you should get json output. Double-check that *uri*'s value is your instance's domain name.
|
In another SSH session/tmux window, check that it is working properly by running `ftp -MVo - http://127.0.0.1:4000/api/v1/instance`, you should get json output. Double-check that *uri*'s value is your instance's domain name.
|
||||||
|
|
||||||
##### Starting akkoma at boot
|
##### Starting akkoma at boot
|
||||||
An rc script to automatically start akkoma at boot hasn't been written yet, it can be run in a tmux session (tmux is in base).
|
An rc script to automatically start akkoma at boot hasn't been written yet, it can be run in a tmux session (tmux is in base).
|
||||||
|
|
||||||
|
|
||||||
#### Create administrative user
|
#### Create administrative user
|
||||||
|
|
||||||
If your instance is up and running, you can create your first user with administrative rights with the following command as the \_akkoma user.
|
If your instance is up and running, you can create your first user with administrative rights with the following command as the `_akkoma` user.
|
||||||
```
|
```
|
||||||
LC_ALL=en_US.UTF-8 MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
|
LC_ALL=en_US.UTF-8 MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
|
||||||
```
|
```
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
This guide covers a installation using an OTP release. To install Akkoma from source, please check out the corresponding guide for your distro.
|
This guide covers a installation using an OTP release. To install Akkoma from source, please check out the corresponding guide for your distro.
|
||||||
|
|
||||||
## Pre-requisites
|
## Pre-requisites
|
||||||
* A machine running Linux with GNU (e.g. Debian, Ubuntu) or musl (e.g. Alpine) libc and `x86_64`, `aarch64` or `armv7l` CPU, you have root access to. If you are not sure if it's compatible see [Detecting flavour section](#detecting-flavour) below
|
* A machine running Linux with GNU (e.g. Debian, Ubuntu) or musl (e.g. Alpine) libc and an `x86_64` CPU you have root access to. If you are not sure if it's compatible see [Detecting flavour section](#detecting-flavour) below
|
||||||
* For installing OTP releases on RedHat-based distros like Fedora and Centos Stream, please follow [this guide](./otp_redhat_en.md) instead.
|
* For installing OTP releases on RedHat-based distros like Fedora and Centos Stream, please follow [this guide](./otp_redhat_en.md) instead.
|
||||||
* A (sub)domain pointed to the machine
|
* A (sub)domain pointed to the machine
|
||||||
|
|
||||||
|
@ -118,8 +118,8 @@ Restart PostgreSQL to apply configuration changes:
|
||||||
adduser --system --shell /bin/false --home /opt/akkoma akkoma
|
adduser --system --shell /bin/false --home /opt/akkoma akkoma
|
||||||
|
|
||||||
# Set the flavour environment variable to the string you got in Detecting flavour section.
|
# Set the flavour environment variable to the string you got in Detecting flavour section.
|
||||||
# For example if the flavour is `amd64-musl` the command will be
|
# For example if the flavour is `amd64` the command will be
|
||||||
export FLAVOUR="amd64-musl"
|
export FLAVOUR="amd64"
|
||||||
|
|
||||||
# Clone the release build into a temporary directory and unpack it
|
# Clone the release build into a temporary directory and unpack it
|
||||||
su akkoma -s $SHELL -lc "
|
su akkoma -s $SHELL -lc "
|
||||||
|
|
|
@ -37,7 +37,7 @@ sudo dnf install git gcc g++ erlang elixir erlang-os_mon erlang-eldap erlang-xme
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cd ~
|
cd ~
|
||||||
git clone https://akkoma.dev/AkkomaGang/akkoma.git
|
git clone https://akkoma.dev/AkkomaGang/akkoma.git -b stable
|
||||||
```
|
```
|
||||||
|
|
||||||
* Change to the new directory:
|
* Change to the new directory:
|
||||||
|
|
|
@ -12,7 +12,7 @@ Release URLs will always be of the form
|
||||||
https://akkoma-updates.s3-website.fr-par.scw.cloud/{branch}/akkoma-{flavour}.zip
|
https://akkoma-updates.s3-website.fr-par.scw.cloud/{branch}/akkoma-{flavour}.zip
|
||||||
```
|
```
|
||||||
|
|
||||||
Where branch is usually `stable` or `develop`, and `flavour` is
|
Where branch is usually `stable` and `flavour` is
|
||||||
the one [that you detect on install](../otp_en/#detecting-flavour).
|
the one [that you detect on install](../otp_en/#detecting-flavour).
|
||||||
|
|
||||||
So, for an AMD64 stable install, your update URL will be
|
So, for an AMD64 stable install, your update URL will be
|
||||||
|
|
|
@ -54,8 +54,6 @@ server {
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
|
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
|
||||||
ssl_prefer_server_ciphers off;
|
ssl_prefer_server_ciphers off;
|
||||||
# In case of an old server with an OpenSSL version of 1.0.2 or below,
|
|
||||||
# leave only prime256v1 or comment out the following line.
|
|
||||||
ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
|
ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
|
||||||
ssl_stapling on;
|
ssl_stapling on;
|
||||||
ssl_stapling_verify on;
|
ssl_stapling_verify on;
|
||||||
|
|
|
@ -79,6 +79,45 @@ def run(["dump", group]) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def run(["dump_to_file", group, key, fname]) do
|
||||||
|
check_configdb(fn ->
|
||||||
|
start_pleroma()
|
||||||
|
|
||||||
|
group = maybe_atomize(group)
|
||||||
|
key = maybe_atomize(key)
|
||||||
|
|
||||||
|
config = ConfigDB.get_by_group_and_key(group, key)
|
||||||
|
|
||||||
|
json =
|
||||||
|
%{
|
||||||
|
group: ConfigDB.to_json_types(config.group),
|
||||||
|
key: ConfigDB.to_json_types(config.key),
|
||||||
|
value: ConfigDB.to_json_types(config.value)
|
||||||
|
}
|
||||||
|
|> Jason.encode!()
|
||||||
|
|> Jason.Formatter.pretty_print()
|
||||||
|
|
||||||
|
File.write(fname, json)
|
||||||
|
shell_info("Wrote #{group}_#{key}.json")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(["load_from_file", fname]) do
|
||||||
|
check_configdb(fn ->
|
||||||
|
start_pleroma()
|
||||||
|
|
||||||
|
json = File.read!(fname)
|
||||||
|
config = Jason.decode!(json)
|
||||||
|
group = ConfigDB.to_elixir_types(config["group"])
|
||||||
|
key = ConfigDB.to_elixir_types(config["key"])
|
||||||
|
value = ConfigDB.to_elixir_types(config["value"])
|
||||||
|
params = %{group: group, key: key, value: value}
|
||||||
|
|
||||||
|
ConfigDB.update_or_create(params)
|
||||||
|
shell_info("Loaded #{config["group"]}, #{config["key"]}")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
def run(["groups"]) do
|
def run(["groups"]) do
|
||||||
check_configdb(fn ->
|
check_configdb(fn ->
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
|
|
@ -67,33 +67,92 @@ def run(["prune_objects" | args]) do
|
||||||
OptionParser.parse(
|
OptionParser.parse(
|
||||||
args,
|
args,
|
||||||
strict: [
|
strict: [
|
||||||
vacuum: :boolean
|
vacuum: :boolean,
|
||||||
|
keep_threads: :boolean,
|
||||||
|
keep_non_public: :boolean
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
|
||||||
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days])
|
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days])
|
||||||
|
time_deadline = NaiveDateTime.utc_now() |> NaiveDateTime.add(-(deadline * 86_400))
|
||||||
|
|
||||||
Logger.info("Pruning objects older than #{deadline} days")
|
log_message = "Pruning objects older than #{deadline} days"
|
||||||
|
|
||||||
time_deadline =
|
log_message =
|
||||||
NaiveDateTime.utc_now()
|
if Keyword.get(options, :keep_non_public) do
|
||||||
|> NaiveDateTime.add(-(deadline * 86_400))
|
log_message <> ", keeping non public posts"
|
||||||
|
else
|
||||||
|
log_message
|
||||||
|
end
|
||||||
|
|
||||||
from(o in Object,
|
log_message =
|
||||||
where:
|
if Keyword.get(options, :keep_threads) do
|
||||||
fragment(
|
log_message <> ", keeping threads intact"
|
||||||
"?->'to' \\? ? OR ?->'cc' \\? ?",
|
else
|
||||||
o.data,
|
log_message
|
||||||
^Pleroma.Constants.as_public(),
|
end
|
||||||
o.data,
|
|
||||||
^Pleroma.Constants.as_public()
|
Logger.info(log_message)
|
||||||
),
|
|
||||||
where: o.inserted_at < ^time_deadline,
|
if Keyword.get(options, :keep_threads) do
|
||||||
where:
|
# We want to delete objects from threads where
|
||||||
|
# 1. the newest post is still old
|
||||||
|
# 2. none of the activities is local
|
||||||
|
# 3. none of the activities is bookmarked
|
||||||
|
# 4. optionally none of the posts is non-public
|
||||||
|
deletable_context =
|
||||||
|
if Keyword.get(options, :keep_non_public) do
|
||||||
|
Pleroma.Activity
|
||||||
|
|> join(:left, [a], b in Pleroma.Bookmark, on: a.id == b.activity_id)
|
||||||
|
|> group_by([a], fragment("? ->> 'context'::text", a.data))
|
||||||
|
|> having(
|
||||||
|
[a],
|
||||||
|
not fragment(
|
||||||
|
# Posts (checked on Create Activity) is non-public
|
||||||
|
"bool_or((not(?->'to' \\? ? OR ?->'cc' \\? ?)) and ? ->> 'type' = 'Create')",
|
||||||
|
a.data,
|
||||||
|
^Pleroma.Constants.as_public(),
|
||||||
|
a.data,
|
||||||
|
^Pleroma.Constants.as_public(),
|
||||||
|
a.data
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Pleroma.Activity
|
||||||
|
|> join(:left, [a], b in Pleroma.Bookmark, on: a.id == b.activity_id)
|
||||||
|
|> group_by([a], fragment("? ->> 'context'::text", a.data))
|
||||||
|
end
|
||||||
|
|> having([a], max(a.updated_at) < ^time_deadline)
|
||||||
|
|> having([a], not fragment("bool_or(?)", a.local))
|
||||||
|
|> having([_, b], fragment("max(?::text) is null", b.id))
|
||||||
|
|> select([a], fragment("? ->> 'context'::text", a.data))
|
||||||
|
|
||||||
|
Pleroma.Object
|
||||||
|
|> where([o], fragment("? ->> 'context'::text", o.data) in subquery(deletable_context))
|
||||||
|
else
|
||||||
|
if Keyword.get(options, :keep_non_public) do
|
||||||
|
Pleroma.Object
|
||||||
|
|> where(
|
||||||
|
[o],
|
||||||
|
fragment(
|
||||||
|
"?->'to' \\? ? OR ?->'cc' \\? ?",
|
||||||
|
o.data,
|
||||||
|
^Pleroma.Constants.as_public(),
|
||||||
|
o.data,
|
||||||
|
^Pleroma.Constants.as_public()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Pleroma.Object
|
||||||
|
end
|
||||||
|
|> where([o], o.updated_at < ^time_deadline)
|
||||||
|
|> where(
|
||||||
|
[o],
|
||||||
fragment("split_part(?->>'actor', '/', 3) != ?", o.data, ^Pleroma.Web.Endpoint.host())
|
fragment("split_part(?->>'actor', '/', 3) != ?", o.data, ^Pleroma.Web.Endpoint.host())
|
||||||
)
|
)
|
||||||
|
end
|
||||||
|> Repo.delete_all(timeout: :infinity)
|
|> Repo.delete_all(timeout: :infinity)
|
||||||
|
|
||||||
prune_hashtags_query = """
|
prune_hashtags_query = """
|
||||||
|
|
109
lib/pleroma/akkoma/translators/argos_translate.ex
Normal file
109
lib/pleroma/akkoma/translators/argos_translate.ex
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
defmodule Pleroma.Akkoma.Translators.ArgosTranslate do
|
||||||
|
@behaviour Pleroma.Akkoma.Translator
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
|
||||||
|
defp argos_translate do
|
||||||
|
Config.get([:argos_translate, :command_argos_translate])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp argospm do
|
||||||
|
Config.get([:argos_translate, :command_argospm])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp strip_html? do
|
||||||
|
Config.get([:argos_translate, :strip_html])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp safe_languages() do
|
||||||
|
try do
|
||||||
|
System.cmd(argospm(), ["list"], stderr_to_stdout: true, parallelism: true)
|
||||||
|
rescue
|
||||||
|
_ -> {"Command #{argospm()} not found", 1}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl Pleroma.Akkoma.Translator
|
||||||
|
def languages do
|
||||||
|
with {response, 0} <- safe_languages() do
|
||||||
|
langs =
|
||||||
|
response
|
||||||
|
|> String.split("\n", trim: true)
|
||||||
|
|> Enum.map(fn
|
||||||
|
"translate-" <> l -> String.split(l, "_")
|
||||||
|
end)
|
||||||
|
|
||||||
|
source_langs =
|
||||||
|
langs
|
||||||
|
|> Enum.map(fn [l, _] -> %{code: l, name: l} end)
|
||||||
|
|> Enum.uniq()
|
||||||
|
|
||||||
|
dest_langs =
|
||||||
|
langs
|
||||||
|
|> Enum.map(fn [_, l] -> %{code: l, name: l} end)
|
||||||
|
|> Enum.uniq()
|
||||||
|
|
||||||
|
{:ok, source_langs, dest_langs}
|
||||||
|
else
|
||||||
|
{response, _} -> {:error, "ArgosTranslate failed to fetch languages (#{response})"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp safe_translate(string, from_language, to_language) do
|
||||||
|
try do
|
||||||
|
System.cmd(
|
||||||
|
argos_translate(),
|
||||||
|
["--from-lang", from_language, "--to-lang", to_language, string],
|
||||||
|
stderr_to_stdout: true,
|
||||||
|
parallelism: true
|
||||||
|
)
|
||||||
|
rescue
|
||||||
|
_ -> {"Command #{argos_translate()} not found", 1}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp clean_string(string, true) do
|
||||||
|
string
|
||||||
|
|> String.replace("<p>", "\n")
|
||||||
|
|> String.replace("</p>", "\n")
|
||||||
|
|> String.replace("<br>", "\n")
|
||||||
|
|> String.replace("<br/>", "\n")
|
||||||
|
|> String.replace("<li>", "\n")
|
||||||
|
|> Pleroma.HTML.strip_tags()
|
||||||
|
|> HtmlEntities.decode()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp clean_string(string, _), do: string
|
||||||
|
|
||||||
|
defp htmlify_response(string, true) do
|
||||||
|
string
|
||||||
|
|> HtmlEntities.encode()
|
||||||
|
|> String.replace("\n", "<br/>")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp htmlify_response(string, _), do: string
|
||||||
|
|
||||||
|
@impl Pleroma.Akkoma.Translator
|
||||||
|
def translate(string, nil, to_language) do
|
||||||
|
# Akkoma's Pleroma-fe expects us to detect the source language automatically.
|
||||||
|
# Argos-translate doesn't have that option (yet?)
|
||||||
|
# see <https://github.com/argosopentech/argos-translate/issues/9>
|
||||||
|
# For now we return the text unchanged, supposedly translated from the target language.
|
||||||
|
# Afterwards people get the option to overwrite the source language from a dropdown.
|
||||||
|
{:ok, to_language, string}
|
||||||
|
end
|
||||||
|
|
||||||
|
def translate(string, from_language, to_language) do
|
||||||
|
# Argos Translate doesn't properly translate HTML (yet?)
|
||||||
|
# For now we give admins the option to strip the html before translating
|
||||||
|
# Note that we have to add some html back to the response afterwards
|
||||||
|
string = clean_string(string, strip_html?())
|
||||||
|
|
||||||
|
with {translated, 0} <-
|
||||||
|
safe_translate(string, from_language, to_language) do
|
||||||
|
{:ok, from_language, translated |> htmlify_response(strip_html?())}
|
||||||
|
else
|
||||||
|
{response, _} -> {:error, "ArgosTranslate failed to translate (#{response})"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -159,7 +159,8 @@ defp cachex_children do
|
||||||
build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000),
|
build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000),
|
||||||
build_cachex("translations", default_ttl: :timer.hours(24 * 30), limit: 2500),
|
build_cachex("translations", default_ttl: :timer.hours(24 * 30), limit: 2500),
|
||||||
build_cachex("instances", default_ttl: :timer.hours(24), ttl_interval: 1000, limit: 2500),
|
build_cachex("instances", default_ttl: :timer.hours(24), ttl_interval: 1000, limit: 2500),
|
||||||
build_cachex("request_signatures", default_ttl: :timer.hours(24 * 30), limit: 3000)
|
build_cachex("request_signatures", default_ttl: :timer.hours(24 * 30), limit: 3000),
|
||||||
|
build_cachex("rel_me", default_ttl: :timer.hours(24 * 30), limit: 300)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ def load_and_update_env(deleted_settings \\ [], restart_pleroma? \\ true) do
|
||||||
# We need to restart applications for loaded settings take effect
|
# We need to restart applications for loaded settings take effect
|
||||||
{logger, other} =
|
{logger, other} =
|
||||||
(Repo.all(ConfigDB) ++ deleted_settings)
|
(Repo.all(ConfigDB) ++ deleted_settings)
|
||||||
|
|> Enum.reject(&invalid_key_or_group/1)
|
||||||
|> Enum.map(&merge_with_default/1)
|
|> Enum.map(&merge_with_default/1)
|
||||||
|> Enum.split_with(fn {group, _, _, _} -> group == :logger end)
|
|> Enum.split_with(fn {group, _, _, _} -> group == :logger end)
|
||||||
|
|
||||||
|
@ -85,6 +86,10 @@ defp maybe_set_pleroma_last(apps) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp invalid_key_or_group(%ConfigDB{key: :invalid_atom}), do: true
|
||||||
|
defp invalid_key_or_group(%ConfigDB{group: :invalid_atom}), do: true
|
||||||
|
defp invalid_key_or_group(_), do: false
|
||||||
|
|
||||||
defp merge_with_default(%{group: group, key: key, value: value} = setting) do
|
defp merge_with_default(%{group: group, key: key, value: value} = setting) do
|
||||||
default =
|
default =
|
||||||
if group == :pleroma do
|
if group == :pleroma do
|
||||||
|
|
|
@ -342,7 +342,11 @@ def string_to_elixir_types(":" <> atom), do: String.to_atom(atom)
|
||||||
|
|
||||||
def string_to_elixir_types(value) do
|
def string_to_elixir_types(value) do
|
||||||
if module_name?(value) do
|
if module_name?(value) do
|
||||||
String.to_existing_atom("Elixir." <> value)
|
try do
|
||||||
|
String.to_existing_atom("Elixir." <> value)
|
||||||
|
rescue
|
||||||
|
ArgumentError -> :invalid_atom
|
||||||
|
end
|
||||||
else
|
else
|
||||||
value
|
value
|
||||||
end
|
end
|
||||||
|
|
|
@ -121,7 +121,7 @@ def user_invitation_email(
|
||||||
"user invitation email body",
|
"user invitation email body",
|
||||||
"""
|
"""
|
||||||
<h3>You are invited to %{instance_name}</h3>
|
<h3>You are invited to %{instance_name}</h3>
|
||||||
<p>%{inviter_name} invites you to join %{instance_name}, an instance of Pleroma federated social networking platform.</p>
|
<p>%{inviter_name} invites you to join %{instance_name}, an instance of Akkoma federated social networking platform.</p>
|
||||||
<p>Click the following link to register: <a href="%{registration_url}">accept invitation</a>.</p>
|
<p>Click the following link to register: <a href="%{registration_url}">accept invitation</a>.</p>
|
||||||
""",
|
""",
|
||||||
instance_name: instance_name(),
|
instance_name: instance_name(),
|
||||||
|
@ -357,7 +357,7 @@ def backup_is_ready_email(backup, admin_user_id \\ nil) do
|
||||||
"static_pages",
|
"static_pages",
|
||||||
"account archive email body - self-requested",
|
"account archive email body - self-requested",
|
||||||
"""
|
"""
|
||||||
<p>You requested a full backup of your Pleroma account. It's ready for download:</p>
|
<p>You requested a full backup of your Akkoma account. It's ready for download:</p>
|
||||||
<p><a href="%{download_url}">%{download_url}</a></p>
|
<p><a href="%{download_url}">%{download_url}</a></p>
|
||||||
""",
|
""",
|
||||||
download_url: download_url
|
download_url: download_url
|
||||||
|
@ -369,7 +369,7 @@ def backup_is_ready_email(backup, admin_user_id \\ nil) do
|
||||||
"static_pages",
|
"static_pages",
|
||||||
"account archive email body - admin requested",
|
"account archive email body - admin requested",
|
||||||
"""
|
"""
|
||||||
<p>Admin @%{admin_nickname} requested a full backup of your Pleroma account. It's ready for download:</p>
|
<p>Admin @%{admin_nickname} requested a full backup of your Akkoma account. It's ready for download:</p>
|
||||||
<p><a href="%{download_url}">%{download_url}</a></p>
|
<p><a href="%{download_url}">%{download_url}</a></p>
|
||||||
""",
|
""",
|
||||||
admin_nickname: admin.nickname,
|
admin_nickname: admin.nickname,
|
||||||
|
|
|
@ -252,7 +252,7 @@ def download(name, url, as) do
|
||||||
|
|
||||||
with :ok <- validate_shareable_packs_available(uri),
|
with :ok <- validate_shareable_packs_available(uri),
|
||||||
{:ok, remote_pack} <-
|
{:ok, remote_pack} <-
|
||||||
uri |> URI.merge("/api/v1/pleroma/emoji/pack?name=#{name}") |> http_get(),
|
uri |> URI.merge("/api/v1/pleroma/emoji/pack?name=#{URI.encode(name)}") |> http_get(),
|
||||||
{:ok, %{sha: sha, url: url} = pack_info} <- fetch_pack_info(remote_pack, uri, name),
|
{:ok, %{sha: sha, url: url} = pack_info} <- fetch_pack_info(remote_pack, uri, name),
|
||||||
{:ok, archive} <- download_archive(url, sha),
|
{:ok, archive} <- download_archive(url, sha),
|
||||||
pack <- copy_as(remote_pack, as || name),
|
pack <- copy_as(remote_pack, as || name),
|
||||||
|
@ -593,7 +593,9 @@ defp fetch_pack_info(remote_pack, uri, name) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%{
|
%{
|
||||||
sha: sha,
|
sha: sha,
|
||||||
url: URI.merge(uri, "/api/v1/pleroma/emoji/packs/archive?name=#{name}") |> to_string()
|
url:
|
||||||
|
URI.merge(uri, "/api/v1/pleroma/emoji/packs/archive?name=#{URI.encode(name)}")
|
||||||
|
|> to_string()
|
||||||
}}
|
}}
|
||||||
|
|
||||||
%{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) ->
|
%{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) ->
|
||||||
|
|
11
lib/pleroma/iso639.ex
Normal file
11
lib/pleroma/iso639.ex
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
defmodule Pleroma.ISO639 do
|
||||||
|
@file "priv/language-codes.json"
|
||||||
|
@data File.read!(@file)
|
||||||
|
|> Jason.decode!()
|
||||||
|
|
||||||
|
for %{"alpha2" => alpha2} <- @data do
|
||||||
|
def valid_alpha2?(unquote(alpha2)), do: true
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_alpha2?(_alpha2), do: false
|
||||||
|
end
|
|
@ -88,9 +88,9 @@ def paginate(query, options, :offset, table_binding) do
|
||||||
|
|
||||||
defp cast_params(params) do
|
defp cast_params(params) do
|
||||||
param_types = %{
|
param_types = %{
|
||||||
min_id: :string,
|
min_id: params[:id_type] || :string,
|
||||||
since_id: :string,
|
since_id: params[:id_type] || :string,
|
||||||
max_id: :string,
|
max_id: params[:id_type] || :string,
|
||||||
offset: :integer,
|
offset: :integer,
|
||||||
limit: :integer,
|
limit: :integer,
|
||||||
skip_extra_order: :boolean,
|
skip_extra_order: :boolean,
|
||||||
|
|
55
lib/pleroma/password.ex
Normal file
55
lib/pleroma/password.ex
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
defmodule Pleroma.Password do
|
||||||
|
@moduledoc """
|
||||||
|
This module handles password hashing and verification.
|
||||||
|
It will delegate to the appropriate module based on the password hash.
|
||||||
|
It also handles upgrading of password hashes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Password.Pbkdf2
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@hashing_module Argon2
|
||||||
|
|
||||||
|
@spec hash_pwd_salt(String.t()) :: String.t()
|
||||||
|
defdelegate hash_pwd_salt(pass), to: @hashing_module
|
||||||
|
|
||||||
|
@spec checkpw(String.t(), String.t()) :: boolean()
|
||||||
|
def checkpw(password, "$2" <> _ = password_hash) do
|
||||||
|
# Handle bcrypt passwords for Mastodon migration
|
||||||
|
Bcrypt.verify_pass(password, password_hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
def checkpw(password, "$pbkdf2" <> _ = password_hash) do
|
||||||
|
Pbkdf2.verify_pass(password, password_hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
def checkpw(password, "$argon2" <> _ = password_hash) do
|
||||||
|
Argon2.verify_pass(password, password_hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
def checkpw(_password, _password_hash) do
|
||||||
|
Logger.error("Password hash not recognized")
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec maybe_update_password(User.t(), String.t()) ::
|
||||||
|
{:ok, User.t()} | {:error, Ecto.Changeset.t()}
|
||||||
|
def maybe_update_password(%User{password_hash: "$2" <> _} = user, password) do
|
||||||
|
do_update_password(user, password)
|
||||||
|
end
|
||||||
|
|
||||||
|
def maybe_update_password(%User{password_hash: "$6" <> _} = user, password) do
|
||||||
|
do_update_password(user, password)
|
||||||
|
end
|
||||||
|
|
||||||
|
def maybe_update_password(%User{password_hash: "$pbkdf2" <> _} = user, password) do
|
||||||
|
do_update_password(user, password)
|
||||||
|
end
|
||||||
|
|
||||||
|
def maybe_update_password(user, _), do: {:ok, user}
|
||||||
|
|
||||||
|
defp do_update_password(user, password) do
|
||||||
|
User.reset_password(user, %{password: password, password_confirmation: password})
|
||||||
|
end
|
||||||
|
end
|
49
lib/pleroma/prometheus_exporter.ex
Normal file
49
lib/pleroma/prometheus_exporter.ex
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
defmodule Pleroma.PrometheusExporter do
|
||||||
|
@moduledoc """
|
||||||
|
Exports metrics in Prometheus format.
|
||||||
|
Mostly exists because of https://github.com/beam-telemetry/telemetry_metrics_prometheus_core/issues/52
|
||||||
|
Basically we need to fetch metrics every so often, or the lib will let them pile up and eventually crash the VM.
|
||||||
|
It also sorta acts as a cache so there is that too.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use GenServer
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
def start_link(_opts) do
|
||||||
|
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def init(_opts) do
|
||||||
|
schedule_next()
|
||||||
|
{:ok, ""}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp schedule_next do
|
||||||
|
Process.send_after(self(), :gather, 60_000)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Scheduled function, gather metrics and schedule next run
|
||||||
|
def handle_info(:gather, _state) do
|
||||||
|
schedule_next()
|
||||||
|
state = TelemetryMetricsPrometheus.Core.scrape()
|
||||||
|
{:noreply, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Trigger the call dynamically, mostly for testing
|
||||||
|
def handle_call(:gather, _from, _state) do
|
||||||
|
state = TelemetryMetricsPrometheus.Core.scrape()
|
||||||
|
{:reply, state, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_call(:show, _from, state) do
|
||||||
|
{:reply, state, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
def show do
|
||||||
|
GenServer.call(__MODULE__, :show)
|
||||||
|
end
|
||||||
|
|
||||||
|
def gather do
|
||||||
|
GenServer.call(__MODULE__, :gather)
|
||||||
|
end
|
||||||
|
end
|
|
@ -14,6 +14,8 @@ defmodule Pleroma.Upload.Filter.Exiftool do
|
||||||
# Formats not compatible with exiftool at this time
|
# Formats not compatible with exiftool at this time
|
||||||
def filter(%Pleroma.Upload{content_type: "image/heic"}), do: {:ok, :noop}
|
def filter(%Pleroma.Upload{content_type: "image/heic"}), do: {:ok, :noop}
|
||||||
def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop}
|
def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop}
|
||||||
|
def filter(%Pleroma.Upload{content_type: "image/svg+xml"}), do: {:ok, :noop}
|
||||||
|
def filter(%Pleroma.Upload{content_type: "image/jxl"}), do: {:ok, :noop}
|
||||||
|
|
||||||
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
|
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
|
||||||
try do
|
try do
|
||||||
|
|
|
@ -479,7 +479,7 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
|
||||||
|> validate_format(:nickname, @email_regex)
|
|> validate_format(:nickname, @email_regex)
|
||||||
|> validate_length(:bio, max: bio_limit)
|
|> validate_length(:bio, max: bio_limit)
|
||||||
|> validate_length(:name, max: name_limit)
|
|> validate_length(:name, max: name_limit)
|
||||||
|> validate_fields(true)
|
|> validate_fields(true, struct)
|
||||||
|> validate_non_local()
|
|> validate_non_local()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -549,7 +549,7 @@ def update_changeset(struct, params \\ %{}) do
|
||||||
:pleroma_settings_store,
|
:pleroma_settings_store,
|
||||||
&{:ok, Map.merge(struct.pleroma_settings_store, &1)}
|
&{:ok, Map.merge(struct.pleroma_settings_store, &1)}
|
||||||
)
|
)
|
||||||
|> validate_fields(false)
|
|> validate_fields(false, struct)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_fields(changeset) do
|
defp put_fields(changeset) do
|
||||||
|
@ -2277,7 +2277,7 @@ def get_ap_ids_by_nicknames(nicknames) do
|
||||||
defp put_password_hash(
|
defp put_password_hash(
|
||||||
%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
|
%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
|
||||||
) do
|
) do
|
||||||
change(changeset, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
|
change(changeset, password_hash: Pleroma.Password.hash_pwd_salt(password))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_password_hash(changeset), do: changeset
|
defp put_password_hash(changeset), do: changeset
|
||||||
|
@ -2359,7 +2359,8 @@ def update_background(user, background) do
|
||||||
|> update_and_set_cache()
|
|> update_and_set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_fields(changeset, remote? \\ false) do
|
@spec validate_fields(Ecto.Changeset.t(), Boolean.t(), User.t()) :: Ecto.Changeset.t()
|
||||||
|
def validate_fields(changeset, remote? \\ false, struct) do
|
||||||
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
|
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
|
||||||
limit = Config.get([:instance, limit_name], 0)
|
limit = Config.get([:instance, limit_name], 0)
|
||||||
|
|
||||||
|
@ -2372,6 +2373,7 @@ def validate_fields(changeset, remote? \\ false) do
|
||||||
[fields: "invalid"]
|
[fields: "invalid"]
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|> maybe_validate_rel_me_field(struct)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp valid_field?(%{"name" => name, "value" => value}) do
|
defp valid_field?(%{"name" => name, "value" => value}) do
|
||||||
|
@ -2384,6 +2386,75 @@ defp valid_field?(%{"name" => name, "value" => value}) do
|
||||||
|
|
||||||
defp valid_field?(_), do: false
|
defp valid_field?(_), do: false
|
||||||
|
|
||||||
|
defp is_url(nil), do: nil
|
||||||
|
|
||||||
|
defp is_url(uri) do
|
||||||
|
case URI.parse(uri) do
|
||||||
|
%URI{host: nil} -> false
|
||||||
|
%URI{scheme: nil} -> false
|
||||||
|
_ -> true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec maybe_validate_rel_me_field(Changeset.t(), User.t()) :: Changeset.t()
|
||||||
|
defp maybe_validate_rel_me_field(changeset, %User{ap_id: _ap_id} = struct) do
|
||||||
|
fields = get_change(changeset, :fields)
|
||||||
|
raw_fields = get_change(changeset, :raw_fields)
|
||||||
|
|
||||||
|
if is_nil(fields) do
|
||||||
|
changeset
|
||||||
|
else
|
||||||
|
validate_rel_me_field(changeset, fields, raw_fields, struct)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_validate_rel_me_field(changeset, _), do: changeset
|
||||||
|
|
||||||
|
@spec validate_rel_me_field(Changeset.t(), [Map.t()], [Map.t()], User.t()) :: Changeset.t()
|
||||||
|
defp validate_rel_me_field(changeset, fields, raw_fields, %User{
|
||||||
|
nickname: nickname,
|
||||||
|
ap_id: ap_id
|
||||||
|
}) do
|
||||||
|
fields =
|
||||||
|
fields
|
||||||
|
|> Enum.with_index()
|
||||||
|
|> Enum.map(fn {%{"name" => name, "value" => value}, index} ->
|
||||||
|
raw_value =
|
||||||
|
if is_nil(raw_fields) do
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
Enum.at(raw_fields, index)["value"]
|
||||||
|
end
|
||||||
|
|
||||||
|
if is_url(raw_value) do
|
||||||
|
frontend_url =
|
||||||
|
Pleroma.Web.Router.Helpers.redirect_url(
|
||||||
|
Pleroma.Web.Endpoint,
|
||||||
|
:redirector_with_meta,
|
||||||
|
nickname
|
||||||
|
)
|
||||||
|
|
||||||
|
possible_urls = [ap_id, frontend_url]
|
||||||
|
|
||||||
|
with "me" <- RelMe.maybe_put_rel_me(raw_value, possible_urls) do
|
||||||
|
%{
|
||||||
|
"name" => name,
|
||||||
|
"value" => value,
|
||||||
|
"verified_at" => DateTime.to_iso8601(DateTime.utc_now())
|
||||||
|
}
|
||||||
|
else
|
||||||
|
e ->
|
||||||
|
Logger.error("Could not check for rel=me, #{inspect(e)}")
|
||||||
|
%{"name" => name, "value" => value}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
%{"name" => name, "value" => value}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
put_change(changeset, :fields, fields)
|
||||||
|
end
|
||||||
|
|
||||||
defp truncate_field(%{"name" => name, "value" => value}) do
|
defp truncate_field(%{"name" => name, "value" => value}) do
|
||||||
{name, _chopped} =
|
{name, _chopped} =
|
||||||
String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
|
String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
|
||||||
|
@ -2551,11 +2622,8 @@ def sanitize_html(%User{} = user) do
|
||||||
# - display name
|
# - display name
|
||||||
def sanitize_html(%User{} = user, filter) do
|
def sanitize_html(%User{} = user, filter) do
|
||||||
fields =
|
fields =
|
||||||
Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
|
Enum.map(user.fields, fn %{"value" => value} = field ->
|
||||||
%{
|
Map.put(field, "value", HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly))
|
||||||
"name" => name,
|
|
||||||
"value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
|
|
||||||
}
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
user
|
user
|
||||||
|
|
|
@ -43,7 +43,13 @@ def get(%User{} = user, %Hashtag{} = hashtag) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_by_user(%User{} = user) do
|
def get_by_user(%User{} = user) do
|
||||||
Ecto.assoc(user, :followed_hashtags)
|
user
|
||||||
|
|> followed_hashtags_query()
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def followed_hashtags_query(%User{} = user) do
|
||||||
|
Ecto.assoc(user, :followed_hashtags)
|
||||||
|
|> Ecto.Query.order_by([h], desc: h.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -132,66 +132,6 @@ defp maybe_halt_on_missing_oauth_scopes_check(conn) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def view do
|
|
||||||
quote do
|
|
||||||
use Phoenix.View,
|
|
||||||
root: "lib/pleroma/web/templates",
|
|
||||||
namespace: Pleroma.Web
|
|
||||||
|
|
||||||
# Import convenience functions from controllers
|
|
||||||
import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
|
|
||||||
|
|
||||||
import Pleroma.Web.ErrorHelpers
|
|
||||||
import Pleroma.Web.Gettext
|
|
||||||
|
|
||||||
alias Pleroma.Web.Router.Helpers, as: Routes
|
|
||||||
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
@doc "Same as `render/3` but wrapped in a rescue block"
|
|
||||||
def safe_render(view, template, assigns \\ %{}) do
|
|
||||||
Phoenix.View.render(view, template, assigns)
|
|
||||||
rescue
|
|
||||||
error ->
|
|
||||||
Logger.error(
|
|
||||||
"#{__MODULE__} failed to render #{inspect({view, template})}\n" <>
|
|
||||||
Exception.format(:error, error, __STACKTRACE__)
|
|
||||||
)
|
|
||||||
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Same as `render_many/4` but wrapped in rescue block.
|
|
||||||
"""
|
|
||||||
def safe_render_many(collection, view, template, assigns \\ %{}) do
|
|
||||||
Enum.map(collection, fn resource ->
|
|
||||||
as = Map.get(assigns, :as) || view.__resource__
|
|
||||||
assigns = Map.put(assigns, as, resource)
|
|
||||||
safe_render(view, template, assigns)
|
|
||||||
end)
|
|
||||||
|> Enum.filter(& &1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def router do
|
|
||||||
quote do
|
|
||||||
use Phoenix.Router
|
|
||||||
# credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
|
|
||||||
import Plug.Conn
|
|
||||||
import Phoenix.Controller
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def channel do
|
|
||||||
quote do
|
|
||||||
# credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
|
|
||||||
import Phoenix.Channel
|
|
||||||
import Pleroma.Web.Gettext
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def plug do
|
def plug do
|
||||||
quote do
|
quote do
|
||||||
@behaviour Pleroma.Web.Plug
|
@behaviour Pleroma.Web.Plug
|
||||||
|
@ -236,6 +176,80 @@ def call(%Plug.Conn{} = conn, options) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def view do
|
||||||
|
quote do
|
||||||
|
use Phoenix.View,
|
||||||
|
root: "lib/pleroma/web/templates",
|
||||||
|
namespace: Pleroma.Web
|
||||||
|
|
||||||
|
# Import convenience functions from controllers
|
||||||
|
import Phoenix.Controller,
|
||||||
|
only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]
|
||||||
|
|
||||||
|
# Include shared imports and aliases for views
|
||||||
|
unquote(view_helpers())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def live_view do
|
||||||
|
quote do
|
||||||
|
use Phoenix.LiveView,
|
||||||
|
layout: {Pleroma.Web.LayoutView, "live.html"}
|
||||||
|
|
||||||
|
unquote(view_helpers())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def live_component do
|
||||||
|
quote do
|
||||||
|
use Phoenix.LiveComponent
|
||||||
|
|
||||||
|
unquote(view_helpers())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def component do
|
||||||
|
quote do
|
||||||
|
use Phoenix.Component
|
||||||
|
|
||||||
|
unquote(view_helpers())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def router do
|
||||||
|
quote do
|
||||||
|
use Phoenix.Router
|
||||||
|
|
||||||
|
import Plug.Conn
|
||||||
|
import Phoenix.Controller
|
||||||
|
import Phoenix.LiveView.Router
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def channel do
|
||||||
|
quote do
|
||||||
|
use Phoenix.Channel
|
||||||
|
import Pleroma.Web.Gettext
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp view_helpers do
|
||||||
|
quote do
|
||||||
|
# Use all HTML functionality (forms, tags, etc)
|
||||||
|
use Phoenix.HTML
|
||||||
|
|
||||||
|
# Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc)
|
||||||
|
import Phoenix.LiveView.Helpers
|
||||||
|
|
||||||
|
# Import basic rendering functionality (render, render_layout, etc)
|
||||||
|
import Phoenix.View
|
||||||
|
|
||||||
|
import Pleroma.Web.ErrorHelpers
|
||||||
|
import Pleroma.Web.Gettext
|
||||||
|
alias Pleroma.Web.Router.Helpers, as: Routes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
When used, dispatch to the appropriate controller/view/etc.
|
When used, dispatch to the appropriate controller/view/etc.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -161,10 +161,17 @@ defp get_policies(_), do: []
|
||||||
# - https://extra.baddomain.net/
|
# - https://extra.baddomain.net/
|
||||||
# Does NOT match the following:
|
# Does NOT match the following:
|
||||||
# - https://maybebaddomain.net/
|
# - https://maybebaddomain.net/
|
||||||
|
|
||||||
|
# *.baddomain.net
|
||||||
def subdomain_regex("*." <> domain), do: subdomain_regex(domain)
|
def subdomain_regex("*." <> domain), do: subdomain_regex(domain)
|
||||||
|
|
||||||
|
# baddomain.net
|
||||||
def subdomain_regex(domain) do
|
def subdomain_regex(domain) do
|
||||||
~r/^(.+\.)?#{Regex.escape(domain)}$/i
|
if String.ends_with?(domain, ".*") do
|
||||||
|
~r/^(.+\.)?#{Regex.escape(String.replace_suffix(domain, ".*", ""))}\.(.+)$/i
|
||||||
|
else
|
||||||
|
~r/^(.+\.)?#{Regex.escape(domain)}$/i
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec subdomains_regex([String.t()]) :: [Regex.t()]
|
@spec subdomains_regex([String.t()]) :: [Regex.t()]
|
||||||
|
|
|
@ -30,6 +30,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
|
||||||
|
|
||||||
field(:replies, {:array, ObjectValidators.ObjectID}, default: [])
|
field(:replies, {:array, ObjectValidators.ObjectID}, default: [])
|
||||||
field(:source, :map)
|
field(:source, :map)
|
||||||
|
field(:contentMap, :map)
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_and_apply(data) do
|
def cast_and_apply(data) do
|
||||||
|
@ -146,6 +147,21 @@ defp fix_source(%{"source" => source} = object) when is_binary(source) do
|
||||||
|
|
||||||
defp fix_source(object), do: object
|
defp fix_source(object), do: object
|
||||||
|
|
||||||
|
defp fix_content_map_languages(%{"contentMap" => content_map} = object)
|
||||||
|
when is_map(content_map) do
|
||||||
|
# Only allow valid languages
|
||||||
|
content_map =
|
||||||
|
content_map
|
||||||
|
|> Enum.reject(fn {lang, _content} ->
|
||||||
|
!Pleroma.ISO639.valid_alpha2?(lang)
|
||||||
|
end)
|
||||||
|
|> Enum.into(%{})
|
||||||
|
|
||||||
|
Map.put(object, "contentMap", content_map)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fix_content_map_languages(object), do: object
|
||||||
|
|
||||||
defp fix(data) do
|
defp fix(data) do
|
||||||
data
|
data
|
||||||
|> CommonFixes.fix_actor()
|
|> CommonFixes.fix_actor()
|
||||||
|
@ -158,6 +174,7 @@ defp fix(data) do
|
||||||
|> Transmogrifier.fix_attachments()
|
|> Transmogrifier.fix_attachments()
|
||||||
|> Transmogrifier.fix_emoji()
|
|> Transmogrifier.fix_emoji()
|
||||||
|> Transmogrifier.fix_content_map()
|
|> Transmogrifier.fix_content_map()
|
||||||
|
|> fix_content_map_languages()
|
||||||
end
|
end
|
||||||
|
|
||||||
def changeset(struct, data) do
|
def changeset(struct, data) do
|
||||||
|
|
|
@ -346,11 +346,16 @@ def fix_tag(%{"tag" => %{} = tag} = object) do
|
||||||
def fix_tag(object), do: object
|
def fix_tag(object), do: object
|
||||||
|
|
||||||
# content map usually only has one language so this will do for now.
|
# content map usually only has one language so this will do for now.
|
||||||
def fix_content_map(%{"contentMap" => content_map} = object) do
|
def fix_content_map(%{"contentMap" => content_map} = object) when is_map(content_map) do
|
||||||
content_groups = Map.to_list(content_map)
|
content_groups = Map.to_list(content_map)
|
||||||
{_, content} = Enum.at(content_groups, 0)
|
|
||||||
|
|
||||||
Map.put(object, "content", content)
|
if Enum.empty?(content_groups) do
|
||||||
|
object
|
||||||
|
else
|
||||||
|
{_, content} = Enum.at(content_groups, 0)
|
||||||
|
|
||||||
|
Map.put(object, "content", content)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_content_map(object), do: object
|
def fix_content_map(object), do: object
|
||||||
|
|
|
@ -14,11 +14,11 @@ defmodule Pleroma.Web.AdminAPI.StatusView do
|
||||||
defdelegate merge_account_views(user), to: AdminAPI.AccountView
|
defdelegate merge_account_views(user), to: AdminAPI.AccountView
|
||||||
|
|
||||||
def render("index.json", %{total: total} = opts) do
|
def render("index.json", %{total: total} = opts) do
|
||||||
%{total: total, activities: safe_render_many(opts.activities, __MODULE__, "show.json", opts)}
|
%{total: total, activities: render_many(opts.activities, __MODULE__, "show.json", opts)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("index.json", opts) do
|
def render("index.json", opts) do
|
||||||
safe_render_many(opts.activities, __MODULE__, "show.json", opts)
|
render_many(opts.activities, __MODULE__, "show.json", opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
|
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
|
||||||
|
|
|
@ -15,7 +15,7 @@ defmodule Pleroma.Web.AkkomaAPI.MetricsController do
|
||||||
def show(conn, _params) do
|
def show(conn, _params) do
|
||||||
if Config.get([:instance, :export_prometheus_metrics], true) do
|
if Config.get([:instance, :export_prometheus_metrics], true) do
|
||||||
conn
|
conn
|
||||||
|> text(TelemetryMetricsPrometheus.Core.scrape())
|
|> text(Pleroma.PrometheusExporter.show())
|
||||||
else
|
else
|
||||||
conn
|
conn
|
||||||
|> send_resp(404, "Not Found")
|
|> send_resp(404, "Not Found")
|
||||||
|
|
|
@ -23,19 +23,19 @@ def spec(opts \\ []) do
|
||||||
[]
|
[]
|
||||||
end,
|
end,
|
||||||
info: %OpenApiSpex.Info{
|
info: %OpenApiSpex.Info{
|
||||||
title: "Pleroma API",
|
title: "Akkoma API",
|
||||||
description: """
|
description: """
|
||||||
This is documentation for client Pleroma API. Most of the endpoints and entities come
|
This is documentation for the Akkoma API. Most of the endpoints and entities come
|
||||||
from Mastodon API and have custom extensions on top.
|
from Mastodon API and have custom extensions on top.
|
||||||
|
|
||||||
While this document aims to be a complete guide to the client API Pleroma exposes,
|
While this document aims to be a complete guide to the client API Akkoma exposes,
|
||||||
the details are still being worked out. Some endpoints may have incomplete or poorly worded documentation.
|
it may not be complete. Some endpoints may have incomplete or poorly worded documentation.
|
||||||
You might want to check the following resources if something is not clear:
|
You might want to check the following resources if something is not clear:
|
||||||
- [Legacy Pleroma-specific endpoint documentation](https://docs-develop.pleroma.social/backend/development/API/pleroma_api/)
|
- [Legacy Pleroma-specific endpoint documentation](https://docs-develop.pleroma.social/backend/development/API/pleroma_api/)
|
||||||
- [Mastodon API documentation](https://docs.joinmastodon.org/client/intro/)
|
- [Mastodon API documentation](https://docs.joinmastodon.org/client/intro/)
|
||||||
- [Differences in Mastodon API responses from vanilla Mastodon](https://docs-develop.pleroma.social/backend/development/API/differences_in_mastoapi_responses/)
|
- [Differences in Mastodon API responses from vanilla Mastodon](https://docs.akkoma.dev/stable/development/API/differences_in_mastoapi_responses/)
|
||||||
|
|
||||||
Please report such occurences on our [issue tracker](https://git.pleroma.social/pleroma/pleroma/-/issues). Feel free to submit API questions or proposals there too!
|
Please report such occurrences on our [issue tracker](https://akkoma.dev/AkkomaGang/akkoma). Feel free to submit API questions or proposals there too!
|
||||||
""",
|
""",
|
||||||
# Strip environment from the version
|
# Strip environment from the version
|
||||||
version: Application.spec(:pleroma, :vsn) |> to_string() |> String.replace(~r/\+.*$/, ""),
|
version: Application.spec(:pleroma, :vsn) |> to_string() |> String.replace(~r/\+.*$/, ""),
|
||||||
|
|
|
@ -432,6 +432,7 @@ def lookup_operation do
|
||||||
],
|
],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => Operation.response("Account", "application/json", Account),
|
200 => Operation.response("Account", "application/json", Account),
|
||||||
|
401 => Operation.response("Error", "application/json", ApiError),
|
||||||
404 => Operation.response("Error", "application/json", ApiError)
|
404 => Operation.response("Error", "application/json", ApiError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ def unfollow_operation do
|
||||||
tags: ["Tags"],
|
tags: ["Tags"],
|
||||||
summary: "Unfollow a hashtag",
|
summary: "Unfollow a hashtag",
|
||||||
description: "Unfollow a hashtag",
|
description: "Unfollow a hashtag",
|
||||||
security: [%{"oAuth" => ["write:follow"]}],
|
security: [%{"oAuth" => ["write:follows"]}],
|
||||||
parameters: [id_param()],
|
parameters: [id_param()],
|
||||||
operationId: "TagController.unfollow",
|
operationId: "TagController.unfollow",
|
||||||
responses: %{
|
responses: %{
|
||||||
|
@ -54,6 +54,26 @@ def unfollow_operation do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def show_followed_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Tags"],
|
||||||
|
summary: "Followed hashtags",
|
||||||
|
description: "View a list of hashtags the currently authenticated user is following",
|
||||||
|
parameters: pagination_params(),
|
||||||
|
security: [%{"oAuth" => ["read:follows"]}],
|
||||||
|
operationId: "TagController.show_followed",
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response("Hashtags", "application/json", %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: Tag
|
||||||
|
}),
|
||||||
|
403 => Operation.response("Forbidden", "application/json", ApiError),
|
||||||
|
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
defp id_param do
|
defp id_param do
|
||||||
Operation.parameter(
|
Operation.parameter(
|
||||||
:id,
|
:id,
|
||||||
|
@ -62,4 +82,22 @@ defp id_param do
|
||||||
"Name of the hashtag"
|
"Name of the hashtag"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def pagination_params do
|
||||||
|
[
|
||||||
|
Operation.parameter(:max_id, :query, :integer, "Return items older than this ID"),
|
||||||
|
Operation.parameter(
|
||||||
|
:min_id,
|
||||||
|
:query,
|
||||||
|
:integer,
|
||||||
|
"Return the oldest items newer than this ID"
|
||||||
|
),
|
||||||
|
Operation.parameter(
|
||||||
|
:limit,
|
||||||
|
:query,
|
||||||
|
%Schema{type: :integer, default: 20},
|
||||||
|
"Maximum number of items to return. Will be ignored if it's more than 40"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,6 +21,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Tag do
|
||||||
following: %Schema{
|
following: %Schema{
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Whether the authenticated user is following the hashtag"
|
description: "Whether the authenticated user is following the hashtag"
|
||||||
|
},
|
||||||
|
history: %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: %Schema{type: :string},
|
||||||
|
description:
|
||||||
|
"A list of historical uses of the hashtag (not implemented, for compatibility only)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
example: %{
|
example: %{
|
||||||
|
|
|
@ -6,7 +6,6 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
|
||||||
alias Pleroma.Registration
|
alias Pleroma.Registration
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.Plugs.AuthenticationPlug
|
|
||||||
|
|
||||||
import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]
|
import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]
|
||||||
|
|
||||||
|
@ -15,8 +14,8 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
|
||||||
def get_user(%Plug.Conn{} = conn) do
|
def get_user(%Plug.Conn{} = conn) do
|
||||||
with {:ok, {name, password}} <- fetch_credentials(conn),
|
with {:ok, {name, password}} <- fetch_credentials(conn),
|
||||||
{_, %User{} = user} <- {:user, fetch_user(name)},
|
{_, %User{} = user} <- {:user, fetch_user(name)},
|
||||||
{_, true} <- {:checkpw, AuthenticationPlug.checkpw(password, user.password_hash)},
|
{_, true} <- {:checkpw, Pleroma.Password.checkpw(password, user.password_hash)},
|
||||||
{:ok, user} <- AuthenticationPlug.maybe_update_password(user, password) do
|
{:ok, user} <- Pleroma.Password.maybe_update_password(user, password) do
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
else
|
else
|
||||||
{:error, _reason} = error -> error
|
{:error, _reason} = error -> error
|
||||||
|
|
|
@ -6,7 +6,6 @@ defmodule Pleroma.Web.Auth.TOTPAuthenticator do
|
||||||
alias Pleroma.MFA
|
alias Pleroma.MFA
|
||||||
alias Pleroma.MFA.TOTP
|
alias Pleroma.MFA.TOTP
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.Plugs.AuthenticationPlug
|
|
||||||
|
|
||||||
@doc "Verify code or check backup code."
|
@doc "Verify code or check backup code."
|
||||||
@spec verify(String.t(), User.t()) ::
|
@spec verify(String.t(), User.t()) ::
|
||||||
|
@ -31,7 +30,7 @@ def verify_recovery_code(
|
||||||
code
|
code
|
||||||
)
|
)
|
||||||
when is_list(codes) and is_binary(code) do
|
when is_list(codes) and is_binary(code) do
|
||||||
hash_code = Enum.find(codes, fn hash -> AuthenticationPlug.checkpw(code, hash) end)
|
hash_code = Enum.find(codes, fn hash -> Pleroma.Password.checkpw(code, hash) end)
|
||||||
|
|
||||||
if hash_code do
|
if hash_code do
|
||||||
MFA.invalidate_backup_code(user, hash_code)
|
MFA.invalidate_backup_code(user, hash_code)
|
||||||
|
|
|
@ -22,6 +22,8 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
attachments: [],
|
attachments: [],
|
||||||
in_reply_to: nil,
|
in_reply_to: nil,
|
||||||
in_reply_to_conversation: nil,
|
in_reply_to_conversation: nil,
|
||||||
|
language: nil,
|
||||||
|
content_map: %{},
|
||||||
quote_id: nil,
|
quote_id: nil,
|
||||||
quote: nil,
|
quote: nil,
|
||||||
visibility: nil,
|
visibility: nil,
|
||||||
|
@ -58,6 +60,7 @@ def create(user, params) do
|
||||||
|> with_valid(&visibility/1)
|
|> with_valid(&visibility/1)
|
||||||
|> with_valid("e_id/1)
|
|> with_valid("e_id/1)
|
||||||
|> content()
|
|> content()
|
||||||
|
|> with_valid(&language/1)
|
||||||
|> with_valid(&to_and_cc/1)
|
|> with_valid(&to_and_cc/1)
|
||||||
|> with_valid(&context/1)
|
|> with_valid(&context/1)
|
||||||
|> sensitive()
|
|> sensitive()
|
||||||
|
@ -133,6 +136,20 @@ defp quote_id(%{params: %{quote_id: %Activity{} = quote}} = draft) do
|
||||||
|
|
||||||
defp quote_id(draft), do: draft
|
defp quote_id(draft), do: draft
|
||||||
|
|
||||||
|
defp language(%{params: %{language: language}, content_html: content} = draft)
|
||||||
|
when is_binary(language) do
|
||||||
|
if Pleroma.ISO639.valid_alpha2?(language) do
|
||||||
|
%__MODULE__{draft | content_map: %{language => content}}
|
||||||
|
else
|
||||||
|
add_error(draft, dgettext("errors", "Invalid language"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp language(%{content_html: content} = draft) do
|
||||||
|
# Use a default language if no language is specified
|
||||||
|
%__MODULE__{draft | content_map: %{"en" => content}}
|
||||||
|
end
|
||||||
|
|
||||||
defp visibility(%{params: params} = draft) do
|
defp visibility(%{params: params} = draft) do
|
||||||
case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do
|
case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do
|
||||||
{visibility, "direct"} when visibility != "direct" ->
|
{visibility, "direct"} when visibility != "direct" ->
|
||||||
|
@ -177,7 +194,7 @@ defp to_and_cc(draft) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp context(draft) do
|
defp context(draft) do
|
||||||
context = Utils.make_context(draft.in_reply_to, draft.in_reply_to_conversation)
|
context = Utils.make_context(draft)
|
||||||
%__MODULE__{draft | context: context}
|
%__MODULE__{draft | context: context}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -224,6 +241,7 @@ defp object(draft) do
|
||||||
"mediaType" => Utils.get_content_type(draft.params[:content_type])
|
"mediaType" => Utils.get_content_type(draft.params[:content_type])
|
||||||
})
|
})
|
||||||
|> Map.put("generator", draft.params[:generator])
|
|> Map.put("generator", draft.params[:generator])
|
||||||
|
|> Map.put("contentMap", draft.content_map)
|
||||||
|
|
||||||
%__MODULE__{draft | object: object}
|
%__MODULE__{draft | object: object}
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,7 +17,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Web.CommonAPI.ActivityDraft
|
alias Pleroma.Web.CommonAPI.ActivityDraft
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
alias Pleroma.Web.Plugs.AuthenticationPlug
|
|
||||||
alias Pleroma.Web.Utils.Params
|
alias Pleroma.Web.Utils.Params
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
@ -231,12 +230,13 @@ def get_content_type(content_type) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_context(_, %Participation{} = participation) do
|
def make_context(%{in_reply_to_conversation: %Participation{} = participation}) do
|
||||||
Repo.preload(participation, :conversation).conversation.ap_id
|
Repo.preload(participation, :conversation).conversation.ap_id
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_context(%Activity{data: %{"context" => context}}, _), do: context
|
def make_context(%{in_reply_to: %Activity{data: %{"context" => context}}}), do: context
|
||||||
def make_context(_, _), do: Utils.generate_context_id()
|
def make_context(%{quote: %Activity{data: %{"context" => context}}}), do: context
|
||||||
|
def make_context(_), do: Utils.generate_context_id()
|
||||||
|
|
||||||
def maybe_add_attachments(parsed, _attachments, false = _no_links), do: parsed
|
def maybe_add_attachments(parsed, _attachments, false = _no_links), do: parsed
|
||||||
|
|
||||||
|
@ -328,20 +328,27 @@ def date_to_asctime(date) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_masto_date(%NaiveDateTime{} = date) do
|
def to_masto_date(%NaiveDateTime{} = date) do
|
||||||
date
|
# NOTE: Elixir’s ISO 8601 format is a superset of the real standard
|
||||||
|> NaiveDateTime.to_iso8601()
|
# It supports negative years for example.
|
||||||
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
|
# ISO8601 only supports years before 1583 with mutual agreement
|
||||||
|
if date.year < 1583 do
|
||||||
|
"1970-01-01T00:00:00Z"
|
||||||
|
else
|
||||||
|
date
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_masto_date(date) when is_binary(date) do
|
def to_masto_date(date) when is_binary(date) do
|
||||||
with {:ok, date} <- NaiveDateTime.from_iso8601(date) do
|
with {:ok, date} <- NaiveDateTime.from_iso8601(date) do
|
||||||
to_masto_date(date)
|
to_masto_date(date)
|
||||||
else
|
else
|
||||||
_ -> ""
|
_ -> "1970-01-01T00:00:00Z"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_masto_date(_), do: ""
|
def to_masto_date(_), do: "1970-01-01T00:00:00Z"
|
||||||
|
|
||||||
defp shortname(name) do
|
defp shortname(name) do
|
||||||
with max_length when max_length > 0 <-
|
with max_length when max_length > 0 <-
|
||||||
|
@ -356,7 +363,7 @@ defp shortname(name) do
|
||||||
@spec confirm_current_password(User.t(), String.t()) :: {:ok, User.t()} | {:error, String.t()}
|
@spec confirm_current_password(User.t(), String.t()) :: {:ok, User.t()} | {:error, String.t()}
|
||||||
def confirm_current_password(user, password) do
|
def confirm_current_password(user, password) do
|
||||||
with %User{local: true} = db_user <- User.get_cached_by_id(user.id),
|
with %User{local: true} = db_user <- User.get_cached_by_id(user.id),
|
||||||
true <- AuthenticationPlug.checkpw(password, db_user.password_hash) do
|
true <- Pleroma.Password.checkpw(password, db_user.password_hash) do
|
||||||
{:ok, db_user}
|
{:ok, db_user}
|
||||||
else
|
else
|
||||||
_ -> {:error, dgettext("errors", "Invalid password.")}
|
_ -> {:error, dgettext("errors", "Invalid password.")}
|
||||||
|
|
|
@ -32,14 +32,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||||
|
|
||||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
|
||||||
plug(:skip_auth when action in [:create, :lookup])
|
plug(:skip_auth when action in [:create])
|
||||||
|
|
||||||
plug(:skip_public_check when action in [:show, :statuses])
|
plug(:skip_public_check when action in [:show, :statuses])
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
|
%{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
|
||||||
when action in [:show, :followers, :following]
|
when action in [:show, :followers, :following, :lookup]
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
|
@ -521,8 +521,9 @@ def blocks(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "GET /api/v1/accounts/lookup"
|
@doc "GET /api/v1/accounts/lookup"
|
||||||
def lookup(conn, %{acct: nickname} = _params) do
|
def lookup(%{assigns: %{user: for_user}} = conn, %{acct: nickname} = _params) do
|
||||||
with %User{} = user <- User.get_by_nickname(nickname) do
|
with %User{} = user <- User.get_by_nickname(nickname),
|
||||||
|
:visible <- User.visible_for(user, for_user) do
|
||||||
render(conn, "show.json",
|
render(conn, "show.json",
|
||||||
user: user,
|
user: user,
|
||||||
skip_visibility_check: true
|
skip_visibility_check: true
|
||||||
|
|
|
@ -4,9 +4,24 @@ defmodule Pleroma.Web.MastodonAPI.TagController do
|
||||||
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Hashtag
|
alias Pleroma.Hashtag
|
||||||
|
alias Pleroma.Pagination
|
||||||
|
|
||||||
|
import Pleroma.Web.ControllerHelper,
|
||||||
|
only: [
|
||||||
|
add_link_headers: 2
|
||||||
|
]
|
||||||
|
|
||||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action in [:show])
|
|
||||||
|
plug(
|
||||||
|
Pleroma.Web.Plugs.OAuthScopesPlug,
|
||||||
|
%{scopes: ["read"]} when action in [:show]
|
||||||
|
)
|
||||||
|
|
||||||
|
plug(
|
||||||
|
Pleroma.Web.Plugs.OAuthScopesPlug,
|
||||||
|
%{scopes: ["read:follows"]} when action in [:show_followed]
|
||||||
|
)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
Pleroma.Web.Plugs.OAuthScopesPlug,
|
Pleroma.Web.Plugs.OAuthScopesPlug,
|
||||||
|
@ -44,4 +59,19 @@ def unfollow(conn, %{id: id}) do
|
||||||
_ -> render_error(conn, :not_found, "Hashtag not found")
|
_ -> render_error(conn, :not_found, "Hashtag not found")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def show_followed(conn, params) do
|
||||||
|
with %{assigns: %{user: %User{} = user}} <- conn do
|
||||||
|
params = Map.put(params, :id_type, :integer)
|
||||||
|
|
||||||
|
hashtags =
|
||||||
|
user
|
||||||
|
|> User.HashtagFollow.followed_hashtags_query()
|
||||||
|
|> Pagination.fetch_paginated(params)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> add_link_headers(hashtags)
|
||||||
|
|> render("index.json", tags: hashtags, for_user: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,7 +12,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
|
||||||
def render("participations.json", %{participations: participations, for: user}) do
|
def render("participations.json", %{participations: participations, for: user}) do
|
||||||
safe_render_many(participations, __MODULE__, "participation.json", %{
|
render_many(participations, __MODULE__, "participation.json", %{
|
||||||
as: :participation,
|
as: :participation,
|
||||||
for: user
|
for: user
|
||||||
})
|
})
|
||||||
|
|
|
@ -66,7 +66,7 @@ def render("index.json", %{notifications: notifications, for: reading_user} = op
|
||||||
|> Map.put(:parent_activities, parent_activities)
|
|> Map.put(:parent_activities, parent_activities)
|
||||||
|> Map.put(:relationships, relationships_opt)
|
|> Map.put(:relationships, relationships_opt)
|
||||||
|
|
||||||
safe_render_many(notifications, NotificationView, "show.json", opts)
|
render_many(notifications, NotificationView, "show.json", opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(
|
def render(
|
||||||
|
|
|
@ -131,7 +131,7 @@ def render("index.json", opts) do
|
||||||
|> Map.put(:parent_activities, parent_activities)
|
|> Map.put(:parent_activities, parent_activities)
|
||||||
|> Map.put(:relationships, relationships_opt)
|
|> Map.put(:relationships, relationships_opt)
|
||||||
|
|
||||||
safe_render_many(activities, StatusView, "show.json", opts)
|
render_many(activities, StatusView, "show.json", opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(
|
def render(
|
||||||
|
@ -169,6 +169,7 @@ def render(
|
||||||
|> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
|
|> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
|
||||||
|
|
||||||
{pinned?, pinned_at} = pin_data(object, user)
|
{pinned?, pinned_at} = pin_data(object, user)
|
||||||
|
lang = language(object)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
id: to_string(activity.id),
|
id: to_string(activity.id),
|
||||||
|
@ -199,7 +200,7 @@ def render(
|
||||||
mentions: mentions,
|
mentions: mentions,
|
||||||
tags: reblogged[:tags] || [],
|
tags: reblogged[:tags] || [],
|
||||||
application: build_application(object.data["generator"]),
|
application: build_application(object.data["generator"]),
|
||||||
language: nil,
|
language: lang,
|
||||||
emojis: [],
|
emojis: [],
|
||||||
pleroma: %{
|
pleroma: %{
|
||||||
local: activity.local,
|
local: activity.local,
|
||||||
|
@ -357,6 +358,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
{pinned?, pinned_at} = pin_data(object, user)
|
{pinned?, pinned_at} = pin_data(object, user)
|
||||||
|
|
||||||
quote = Activity.get_quoted_activity_from_object(object)
|
quote = Activity.get_quoted_activity_from_object(object)
|
||||||
|
lang = language(object)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
id: to_string(activity.id),
|
id: to_string(activity.id),
|
||||||
|
@ -391,7 +393,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
mentions: mentions,
|
mentions: mentions,
|
||||||
tags: build_tags(tags),
|
tags: build_tags(tags),
|
||||||
application: build_application(object.data["generator"]),
|
application: build_application(object.data["generator"]),
|
||||||
language: nil,
|
language: lang,
|
||||||
emojis: build_emojis(object.data["emoji"]),
|
emojis: build_emojis(object.data["emoji"]),
|
||||||
quote_id: if(quote, do: quote.id, else: nil),
|
quote_id: if(quote, do: quote.id, else: nil),
|
||||||
quote: maybe_render_quote(quote, opts),
|
quote: maybe_render_quote(quote, opts),
|
||||||
|
@ -784,4 +786,12 @@ defp get_source_content_type(%{"mediaType" => type} = _source) do
|
||||||
defp get_source_content_type(_source) do
|
defp get_source_content_type(_source) do
|
||||||
Utils.get_content_type(nil)
|
Utils.get_content_type(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp language(%Object{data: %{"contentMap" => contentMap}}) when is_map(contentMap) do
|
||||||
|
contentMap
|
||||||
|
|> Map.keys()
|
||||||
|
|> Enum.at(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp language(_), do: nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,10 @@ defmodule Pleroma.Web.MastodonAPI.TagView do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.Router.Helpers
|
alias Pleroma.Web.Router.Helpers
|
||||||
|
|
||||||
|
def render("index.json", %{tags: tags, for_user: user}) do
|
||||||
|
render_many(tags, __MODULE__, "show.json", %{for_user: user})
|
||||||
|
end
|
||||||
|
|
||||||
def render("show.json", %{tag: tag, for_user: user}) do
|
def render("show.json", %{tag: tag, for_user: user}) do
|
||||||
following =
|
following =
|
||||||
with %User{} <- user do
|
with %User{} <- user do
|
||||||
|
|
|
@ -7,7 +7,6 @@ defmodule Pleroma.Web.MongooseIM.MongooseIMController do
|
||||||
|
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.Plugs.AuthenticationPlug
|
|
||||||
alias Pleroma.Web.Plugs.RateLimiter
|
alias Pleroma.Web.Plugs.RateLimiter
|
||||||
|
|
||||||
plug(RateLimiter, [name: :authentication] when action in [:user_exists, :check_password])
|
plug(RateLimiter, [name: :authentication] when action in [:user_exists, :check_password])
|
||||||
|
@ -28,7 +27,7 @@ def user_exists(conn, %{"user" => username}) do
|
||||||
def check_password(conn, %{"user" => username, "pass" => password}) do
|
def check_password(conn, %{"user" => username, "pass" => password}) do
|
||||||
with %User{password_hash: password_hash, is_active: true} <-
|
with %User{password_hash: password_hash, is_active: true} <-
|
||||||
Repo.get_by(User, nickname: username, local: true),
|
Repo.get_by(User, nickname: username, local: true),
|
||||||
true <- AuthenticationPlug.checkpw(password, password_hash) do
|
true <- Pleroma.Password.checkpw(password, password_hash) do
|
||||||
conn
|
conn
|
||||||
|> json(true)
|
|> json(true)
|
||||||
else
|
else
|
||||||
|
|
|
@ -61,7 +61,7 @@ def to_string(scopes), do: Enum.join(scopes, " ")
|
||||||
def validate(blank_scopes, _app_scopes, _user) when blank_scopes in [nil, []],
|
def validate(blank_scopes, _app_scopes, _user) when blank_scopes in [nil, []],
|
||||||
do: {:error, :missing_scopes}
|
do: {:error, :missing_scopes}
|
||||||
|
|
||||||
def validate(scopes, app_scopes, %Pleroma.User{is_admin: is_admin}) do
|
def validate(scopes, app_scopes, _user) do
|
||||||
validate_scopes_are_supported(scopes, app_scopes)
|
validate_scopes_are_supported(scopes, app_scopes)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do
|
||||||
|
|
||||||
alias Pleroma.Helpers.AuthHelper
|
alias Pleroma.Helpers.AuthHelper
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Password
|
||||||
|
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
|
||||||
|
@ -25,8 +26,8 @@ def call(
|
||||||
} = conn,
|
} = conn,
|
||||||
_
|
_
|
||||||
) do
|
) do
|
||||||
if checkpw(password, password_hash) do
|
if Password.checkpw(password, password_hash) do
|
||||||
{:ok, auth_user} = maybe_update_password(auth_user, password)
|
{:ok, auth_user} = Password.maybe_update_password(auth_user, password)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> assign(:user, auth_user)
|
|> assign(:user, auth_user)
|
||||||
|
@ -38,35 +39,6 @@ def call(
|
||||||
|
|
||||||
def call(conn, _), do: conn
|
def call(conn, _), do: conn
|
||||||
|
|
||||||
def checkpw(password, "$6" <> _ = password_hash) do
|
@spec checkpw(String.t(), String.t()) :: boolean
|
||||||
:crypt.crypt(password, password_hash) == password_hash
|
defdelegate checkpw(password, hash), to: Password
|
||||||
end
|
|
||||||
|
|
||||||
def checkpw(password, "$2" <> _ = password_hash) do
|
|
||||||
# Handle bcrypt passwords for Mastodon migration
|
|
||||||
Bcrypt.verify_pass(password, password_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def checkpw(password, "$pbkdf2" <> _ = password_hash) do
|
|
||||||
Pleroma.Password.Pbkdf2.verify_pass(password, password_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def checkpw(_password, _password_hash) do
|
|
||||||
Logger.error("Password hash not recognized")
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def maybe_update_password(%User{password_hash: "$2" <> _} = user, password) do
|
|
||||||
do_update_password(user, password)
|
|
||||||
end
|
|
||||||
|
|
||||||
def maybe_update_password(%User{password_hash: "$6" <> _} = user, password) do
|
|
||||||
do_update_password(user, password)
|
|
||||||
end
|
|
||||||
|
|
||||||
def maybe_update_password(user, _), do: {:ok, user}
|
|
||||||
|
|
||||||
defp do_update_password(user, password) do
|
|
||||||
User.reset_password(user, %{password: password, password_confirmation: password})
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
31
lib/pleroma/web/plugs/ensure_http_signature_plug.ex
Normal file
31
lib/pleroma/web/plugs/ensure_http_signature_plug.ex
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# Akkoma: Magically expressive social media
|
||||||
|
# Copyright © 2022-2022 Akkoma Authors <https://akkoma.dev/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.Plugs.EnsureHTTPSignaturePlug do
|
||||||
|
@moduledoc """
|
||||||
|
Ensures HTTP signature has been validated by previous plugs on ActivityPub requests.
|
||||||
|
"""
|
||||||
|
import Plug.Conn
|
||||||
|
import Phoenix.Controller, only: [get_format: 1, text: 2]
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
|
||||||
|
def init(options) do
|
||||||
|
options
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(%{assigns: %{valid_signature: true}} = conn, _), do: conn
|
||||||
|
|
||||||
|
def call(conn, _) do
|
||||||
|
with true <- get_format(conn) in ["json", "activity+json"],
|
||||||
|
true <- Config.get([:activitypub, :authorized_fetch_mode], true) do
|
||||||
|
conn
|
||||||
|
|> put_status(:unauthorized)
|
||||||
|
|> text("Request not signed")
|
||||||
|
|> halt()
|
||||||
|
else
|
||||||
|
_ -> conn
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -106,20 +106,15 @@ defp csp_string(conn) do
|
||||||
connect_src =
|
connect_src =
|
||||||
if Config.get([:media_proxy, :enabled]) do
|
if Config.get([:media_proxy, :enabled]) do
|
||||||
sources = build_csp_multimedia_source_list()
|
sources = build_csp_multimedia_source_list()
|
||||||
["connect-src 'self' blob: ", static_url, ?\s, websocket_url, ?\s, sources]
|
["connect-src 'self' ", static_url, ?\s, websocket_url, ?\s, sources]
|
||||||
else
|
else
|
||||||
["connect-src 'self' blob: ", static_url, ?\s, websocket_url]
|
["connect-src 'self' ", static_url, ?\s, websocket_url]
|
||||||
end
|
end
|
||||||
|
|
||||||
style_src = "style-src 'self' 'unsafe-inline'"
|
style_src = "style-src 'self' '#{nonce_tag}'"
|
||||||
font_src = "font-src 'self' data:"
|
font_src = "font-src 'self'"
|
||||||
|
|
||||||
script_src =
|
script_src = "script-src 'self' 'unsafe-eval' '#{nonce_tag}'"
|
||||||
if Config.get(:env) == :dev do
|
|
||||||
"script-src 'self' 'unsafe-eval' '#{nonce_tag}'"
|
|
||||||
else
|
|
||||||
"script-src 'self' 'unsafe-eval' '#{nonce_tag}'"
|
|
||||||
end
|
|
||||||
|
|
||||||
report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"]
|
report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"]
|
||||||
insecure = if scheme == "https", do: "upgrade-insecure-requests"
|
insecure = if scheme == "https", do: "upgrade-insecure-requests"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
import Phoenix.Controller, only: [get_format: 1, text: 2]
|
import Phoenix.Controller, only: [get_format: 1]
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Web.Router
|
alias Pleroma.Web.Router
|
||||||
alias Pleroma.Signature
|
alias Pleroma.Signature
|
||||||
|
@ -22,7 +22,7 @@ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(conn, _opts) do
|
def call(conn, _opts) do
|
||||||
if get_format(conn) == "activity+json" do
|
if get_format(conn) in ["json", "activity+json"] do
|
||||||
conn
|
conn
|
||||||
|> maybe_assign_valid_signature()
|
|> maybe_assign_valid_signature()
|
||||||
|> maybe_require_signature()
|
|> maybe_require_signature()
|
||||||
|
@ -113,18 +113,7 @@ defp maybe_require_signature(
|
||||||
conn
|
conn
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_require_signature(%{assigns: %{valid_signature: true}} = conn), do: conn
|
defp maybe_require_signature(conn), do: conn
|
||||||
|
|
||||||
defp maybe_require_signature(conn) do
|
|
||||||
if Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) do
|
|
||||||
conn
|
|
||||||
|> put_status(:unauthorized)
|
|
||||||
|> text("Request not signed")
|
|
||||||
|> halt()
|
|
||||||
else
|
|
||||||
conn
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp signature_host(conn) do
|
defp signature_host(conn) do
|
||||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
||||||
|
|
|
@ -38,7 +38,6 @@ defp parse_url(url) do
|
||||||
|
|
||||||
def maybe_put_rel_me("http" <> _ = target_page, profile_urls) when is_list(profile_urls) do
|
def maybe_put_rel_me("http" <> _ = target_page, profile_urls) when is_list(profile_urls) do
|
||||||
{:ok, rel_me_hrefs} = parse(target_page)
|
{:ok, rel_me_hrefs} = parse(target_page)
|
||||||
|
|
||||||
true = Enum.any?(rel_me_hrefs, fn x -> x in profile_urls end)
|
true = Enum.any?(rel_me_hrefs, fn x -> x in profile_urls end)
|
||||||
|
|
||||||
"me"
|
"me"
|
||||||
|
|
|
@ -15,7 +15,7 @@ def parse(nil), do: {:error, "No URL provided"}
|
||||||
|
|
||||||
if Pleroma.Config.get(:env) == :test do
|
if Pleroma.Config.get(:env) == :test do
|
||||||
@spec parse(String.t()) :: {:ok, map()} | {:error, any()}
|
@spec parse(String.t()) :: {:ok, map()} | {:error, any()}
|
||||||
def parse(url), do: parse_url(url)
|
def parse(url), do: parse_with_timeout(url)
|
||||||
else
|
else
|
||||||
@spec parse(String.t()) :: {:ok, map()} | {:error, any()}
|
@spec parse(String.t()) :: {:ok, map()} | {:error, any()}
|
||||||
def parse(url) do
|
def parse(url) do
|
||||||
|
@ -27,7 +27,7 @@ def parse(url) do
|
||||||
|
|
||||||
defp get_cached_or_parse(url) do
|
defp get_cached_or_parse(url) do
|
||||||
case @cachex.fetch(:rich_media_cache, url, fn ->
|
case @cachex.fetch(:rich_media_cache, url, fn ->
|
||||||
case parse_url(url) do
|
case parse_with_timeout(url) do
|
||||||
{:ok, _} = res ->
|
{:ok, _} = res ->
|
||||||
{:commit, res}
|
{:commit, res}
|
||||||
|
|
||||||
|
@ -141,6 +141,21 @@ def parse_url(url) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def parse_with_timeout(url) do
|
||||||
|
try do
|
||||||
|
task =
|
||||||
|
Task.Supervisor.async_nolink(Pleroma.TaskSupervisor, fn ->
|
||||||
|
parse_url(url)
|
||||||
|
end)
|
||||||
|
|
||||||
|
Task.await(task, 5000)
|
||||||
|
catch
|
||||||
|
:exit, {:timeout, _} ->
|
||||||
|
Logger.warn("Timeout while fetching rich media for #{url}")
|
||||||
|
{:error, :timeout}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp maybe_parse(html) do
|
defp maybe_parse(html) do
|
||||||
Enum.reduce_while(parsers(), %{}, fn parser, acc ->
|
Enum.reduce_while(parsers(), %{}, fn parser, acc ->
|
||||||
case parser.parse(html, acc) do
|
case parser.parse(html, acc) do
|
||||||
|
|
|
@ -147,6 +147,7 @@ defmodule Pleroma.Web.Router do
|
||||||
pipeline :http_signature do
|
pipeline :http_signature do
|
||||||
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
|
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
|
||||||
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
|
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
|
||||||
|
plug(Pleroma.Web.Plugs.EnsureHTTPSignaturePlug)
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :static_fe do
|
pipeline :static_fe do
|
||||||
|
@ -605,6 +606,7 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/tags/:id", TagController, :show)
|
get("/tags/:id", TagController, :show)
|
||||||
post("/tags/:id/follow", TagController, :follow)
|
post("/tags/:id/follow", TagController, :follow)
|
||||||
post("/tags/:id/unfollow", TagController, :unfollow)
|
post("/tags/:id/unfollow", TagController, :unfollow)
|
||||||
|
get("/followed_tags", TagController, :show_followed)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/web", Pleroma.Web do
|
scope "/api/web", Pleroma.Web do
|
||||||
|
@ -919,7 +921,7 @@ defmodule Pleroma.Web.Router do
|
||||||
|
|
||||||
# TODO: Change to Phoenix.Router.routes/1 for Phoenix 1.6.0+
|
# TODO: Change to Phoenix.Router.routes/1 for Phoenix 1.6.0+
|
||||||
def get_api_routes do
|
def get_api_routes do
|
||||||
__MODULE__.__routes__()
|
Phoenix.Router.routes(__MODULE__)
|
||||||
|> Enum.reject(fn r -> r.plug == Pleroma.Web.Fallback.RedirectController end)
|
|> Enum.reject(fn r -> r.plug == Pleroma.Web.Fallback.RedirectController end)
|
||||||
|> Enum.map(fn r ->
|
|> Enum.map(fn r ->
|
||||||
r.path
|
r.path
|
||||||
|
|
|
@ -25,7 +25,7 @@ def show(%{assigns: %{notice_id: notice_id}} = conn, _params) do
|
||||||
true <- Visibility.is_public?(activity.object),
|
true <- Visibility.is_public?(activity.object),
|
||||||
{_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)},
|
{_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)},
|
||||||
%User{} = user <- User.get_by_ap_id(activity.object.data["actor"]) do
|
%User{} = user <- User.get_by_ap_id(activity.object.data["actor"]) do
|
||||||
meta = Metadata.build_tags(%{activity_id: notice_id, object: activity.object, user: user})
|
meta = Metadata.build_tags(%{url: activity.data["id"], object: activity.object, user: user})
|
||||||
|
|
||||||
timeline =
|
timeline =
|
||||||
activity.object.data["context"]
|
activity.object.data["context"]
|
||||||
|
|
|
@ -2,6 +2,7 @@ defmodule Pleroma.Web.Telemetry do
|
||||||
use Supervisor
|
use Supervisor
|
||||||
import Telemetry.Metrics
|
import Telemetry.Metrics
|
||||||
alias Pleroma.Stats
|
alias Pleroma.Stats
|
||||||
|
alias Pleroma.Config
|
||||||
|
|
||||||
def start_link(arg) do
|
def start_link(arg) do
|
||||||
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
|
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
|
||||||
|
@ -9,14 +10,28 @@ def start_link(arg) do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def init(_arg) do
|
def init(_arg) do
|
||||||
children = [
|
children =
|
||||||
{:telemetry_poller, measurements: periodic_measurements(), period: 10_000},
|
[
|
||||||
{TelemetryMetricsPrometheus.Core, metrics: prometheus_metrics()}
|
{:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
|
||||||
]
|
] ++
|
||||||
|
prometheus_children()
|
||||||
|
|
||||||
Supervisor.init(children, strategy: :one_for_one)
|
Supervisor.init(children, strategy: :one_for_one)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp prometheus_children do
|
||||||
|
config = Config.get([:instance, :export_prometheus_metrics], true)
|
||||||
|
|
||||||
|
if config do
|
||||||
|
[
|
||||||
|
{TelemetryMetricsPrometheus.Core, metrics: prometheus_metrics()},
|
||||||
|
Pleroma.PrometheusExporter
|
||||||
|
]
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# A seperate set of metrics for distributions because phoenix dashboard does NOT handle them well
|
# A seperate set of metrics for distributions because phoenix dashboard does NOT handle them well
|
||||||
defp distribution_metrics do
|
defp distribution_metrics do
|
||||||
[
|
[
|
||||||
|
|
|
@ -18,10 +18,21 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
||||||
action_fallback(:errors)
|
action_fallback(:errors)
|
||||||
|
|
||||||
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
|
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
|
||||||
with %User{} = user <- User.get_cached_by_id(uid),
|
case User.get_cached_by_id(uid) do
|
||||||
true <- user.local and !user.is_confirmed and user.confirmation_token == token,
|
%User{local: true, is_confirmed: false, confirmation_token: ^token} = user ->
|
||||||
{:ok, _} <- User.confirm(user) do
|
case User.confirm(user) do
|
||||||
redirect(conn, to: "/")
|
{:ok, _} ->
|
||||||
|
redirect(conn, to: "/")
|
||||||
|
|
||||||
|
{:error, _} ->
|
||||||
|
json_reply(conn, 400, "Unable to confirm")
|
||||||
|
end
|
||||||
|
|
||||||
|
%User{is_confirmed: true} ->
|
||||||
|
json_reply(conn, 400, "Already verified email")
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
json_reply(conn, 400, "Couldn't verify email")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
6
mix.exs
6
mix.exs
|
@ -143,9 +143,7 @@ defp deps do
|
||||||
{:sweet_xml, "~> 0.7.2"},
|
{:sweet_xml, "~> 0.7.2"},
|
||||||
{:earmark, "~> 1.4.15"},
|
{:earmark, "~> 1.4.15"},
|
||||||
{:bbcode_pleroma, "~> 0.2.0"},
|
{:bbcode_pleroma, "~> 0.2.0"},
|
||||||
{:crypt,
|
{:argon2_elixir, "~> 3.0.0"},
|
||||||
git: "https://github.com/msantos/crypt.git",
|
|
||||||
ref: "f75cd55325e33cbea198fb41fe41871392f8fb76"},
|
|
||||||
{:cors_plug, "~> 2.0"},
|
{:cors_plug, "~> 2.0"},
|
||||||
{:web_push_encryption, "~> 0.3.1"},
|
{:web_push_encryption, "~> 0.3.1"},
|
||||||
{:swoosh, "~> 1.0"},
|
{:swoosh, "~> 1.0"},
|
||||||
|
@ -202,7 +200,7 @@ defp deps do
|
||||||
ref: "1c1b99ea41a457761383d81aaf6a606913996fe7",
|
ref: "1c1b99ea41a457761383d81aaf6a606913996fe7",
|
||||||
only: [:dev, :test],
|
only: [:dev, :test],
|
||||||
runtime: false},
|
runtime: false},
|
||||||
{:mock, "~> 0.3.5", only: :test},
|
{:mock, "~> 0.3.7", only: :test},
|
||||||
{:excoveralls, "0.15.1", only: :test},
|
{:excoveralls, "0.15.1", only: :test},
|
||||||
{:mox, "~> 1.0", only: :test},
|
{:mox, "~> 1.0", only: :test},
|
||||||
{:websockex, "~> 0.4.3", only: :test},
|
{:websockex, "~> 0.4.3", only: :test},
|
||||||
|
|
20
mix.lock
20
mix.lock
|
@ -1,10 +1,11 @@
|
||||||
%{
|
%{
|
||||||
|
"argon2_elixir": {:hex, :argon2_elixir, "3.0.0", "fd4405f593e77b525a5c667282172dd32772d7c4fa58cdecdaae79d2713b6c5f", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "8b753b270af557d51ba13fcdebc0f0ab27a2a6792df72fd5a6cf9cfaffcedc57"},
|
||||||
"base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"},
|
"base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"},
|
||||||
"bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
|
"bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
|
||||||
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.1", "5114d780459a04f2b4aeef52307de23de961b69e13a5cd98a911e39fda13f420", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "42182d5f46764def15bf9af83739e3bf4ad22661b1c34fc3e88558efced07279"},
|
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.1", "5114d780459a04f2b4aeef52307de23de961b69e13a5cd98a911e39fda13f420", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "42182d5f46764def15bf9af83739e3bf4ad22661b1c34fc3e88558efced07279"},
|
||||||
"benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"},
|
"benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"},
|
||||||
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
|
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
|
||||||
"cachex": {:hex, :cachex, "3.4.0", "868b2959ea4aeb328c6b60ff66c8d5123c083466ad3c33d3d8b5f142e13101fb", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "370123b1ab4fba4d2965fb18f87fd758325709787c8c5fce35b3fe80645ccbe5"},
|
"cachex": {:hex, :cachex, "3.5.0", "f715390a9e93125980187dcd7c4036ece92d273fbd9ec009a8ffa480abdc51f8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "fac2ebfa200dd9ffba08cdcef404426ccadfcb92281ca34f810535712d02b049"},
|
||||||
"calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.1.201603 or ~> 0.5.20 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
|
"calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.1.201603 or ~> 0.5.20 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
|
||||||
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e0f16822d578866e186a0974d65ad58cddc1e2ab", [ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"]},
|
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e0f16822d578866e186a0974d65ad58cddc1e2ab", [ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"]},
|
||||||
"castore": {:hex, :castore, "0.1.20", "62a0126cbb7cb3e259257827b9190f88316eb7aa3fdac01fd6f2dfd64e7f46e9", [:mix], [], "hexpm", "a020b7650529c986c454a4035b6b13a328e288466986307bea3aadb4c95ac98a"},
|
"castore": {:hex, :castore, "0.1.20", "62a0126cbb7cb3e259257827b9190f88316eb7aa3fdac01fd6f2dfd64e7f46e9", [:mix], [], "hexpm", "a020b7650529c986c454a4035b6b13a328e288466986307bea3aadb4c95ac98a"},
|
||||||
|
@ -18,7 +19,6 @@
|
||||||
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"},
|
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"},
|
||||||
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
|
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
|
||||||
"credo": {:git, "https://github.com/rrrene/credo.git", "1c1b99ea41a457761383d81aaf6a606913996fe7", [ref: "1c1b99ea41a457761383d81aaf6a606913996fe7"]},
|
"credo": {:git, "https://github.com/rrrene/credo.git", "1c1b99ea41a457761383d81aaf6a606913996fe7", [ref: "1c1b99ea41a457761383d81aaf6a606913996fe7"]},
|
||||||
"crypt": {:git, "https://github.com/msantos/crypt.git", "f75cd55325e33cbea198fb41fe41871392f8fb76", [ref: "f75cd55325e33cbea198fb41fe41871392f8fb76"]},
|
|
||||||
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
|
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
|
||||||
"db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"},
|
"db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"},
|
||||||
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
|
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
|
||||||
|
@ -27,16 +27,16 @@
|
||||||
"earmark": {:hex, :earmark, "1.4.34", "d7f89d3bbd7567a0bffc465e0a949f8f8dcbe43909c3acf96f4761a302cea10c", [:mix], [{:earmark_parser, "~> 1.4.29", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "90b106f3dad85b133b10d7d628167c88246123fd1cecb4557d83d21ec9e65504"},
|
"earmark": {:hex, :earmark, "1.4.34", "d7f89d3bbd7567a0bffc465e0a949f8f8dcbe43909c3acf96f4761a302cea10c", [:mix], [{:earmark_parser, "~> 1.4.29", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "90b106f3dad85b133b10d7d628167c88246123fd1cecb4557d83d21ec9e65504"},
|
||||||
"earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"},
|
"earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"},
|
||||||
"eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"},
|
"eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"},
|
||||||
"ecto": {:hex, :ecto, "3.9.2", "017db3bc786ff64271108522c01a5d3f6ba0aea5c84912cfb0dd73bf13684108", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "21466d5177e09e55289ac7eade579a642578242c7a3a9f91ad5c6583337a9d15"},
|
"ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"},
|
||||||
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
|
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
|
||||||
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.10", "e14d400930f401ca9f541b3349212634e44027d7f919bbb71224d7ac0d0e8acd", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "505e8cd81e4f17c090be0f99e92b1b3f0fd915f98e76965130b8ccfb891e7088"},
|
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.10", "e14d400930f401ca9f541b3349212634e44027d7f919bbb71224d7ac0d0e8acd", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "505e8cd81e4f17c090be0f99e92b1b3f0fd915f98e76965130b8ccfb891e7088"},
|
||||||
"ecto_sql": {:hex, :ecto_sql, "3.9.1", "9bd5894eecc53d5b39d0c95180d4466aff00e10679e13a5cfa725f6f85c03c22", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5fd470a4fff2e829bbf9dcceb7f3f9f6d1e49b4241e802f614de6b8b67c51118"},
|
"ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"},
|
||||||
"elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]},
|
"elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]},
|
||||||
"elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"},
|
"elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"},
|
||||||
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
|
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
|
||||||
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
|
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
|
||||||
"ex_aws": {:hex, :ex_aws, "2.1.9", "dc4865ecc20a05190a34a0ac5213e3e5e2b0a75a0c2835e923ae7bfeac5e3c31", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "3e6c776703c9076001fbe1f7c049535f042cb2afa0d2cbd3b47cbc4e92ac0d10"},
|
"ex_aws": {:hex, :ex_aws, "2.1.9", "dc4865ecc20a05190a34a0ac5213e3e5e2b0a75a0c2835e923ae7bfeac5e3c31", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "3e6c776703c9076001fbe1f7c049535f042cb2afa0d2cbd3b47cbc4e92ac0d10"},
|
||||||
"ex_aws_s3": {:hex, :ex_aws_s3, "2.3.3", "61412e524616ea31d3f31675d8bc4c73f277e367dee0ae8245610446f9b778aa", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0044f0b6f9ce925666021eafd630de64c2b3404d79c85245cc7c8a9a32d7f104"},
|
"ex_aws_s3": {:hex, :ex_aws_s3, "2.4.0", "ce8decb6b523381812798396bc0e3aaa62282e1b40520125d1f4eff4abdff0f4", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "85dda6e27754d94582869d39cba3241d9ea60b6aa4167f9c88e309dc687e56bb"},
|
||||||
"ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"},
|
"ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"},
|
||||||
"ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"},
|
"ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"},
|
||||||
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
|
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
|
||||||
|
@ -59,7 +59,7 @@
|
||||||
"inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"},
|
"inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"},
|
||||||
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
|
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
|
||||||
"joken": {:hex, :joken, "2.5.0", "09be497d804b8115eb6f07615cef2e60c2a1008fb89dc0aef0d4c4b4609b99aa", [:mix], [{:jose, "~> 1.11.2", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "22b25c89617c5ed8ca7b31026340a25ea0f9ca7160f9706b79be9ed81fdf74e7"},
|
"joken": {:hex, :joken, "2.5.0", "09be497d804b8115eb6f07615cef2e60c2a1008fb89dc0aef0d4c4b4609b99aa", [:mix], [{:jose, "~> 1.11.2", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "22b25c89617c5ed8ca7b31026340a25ea0f9ca7160f9706b79be9ed81fdf74e7"},
|
||||||
"jose": {:hex, :jose, "1.11.2", "f4c018ccf4fdce22c71e44d471f15f723cb3efab5d909ab2ba202b5bf35557b3", [:mix, :rebar3], [], "hexpm", "98143fbc48d55f3a18daba82d34fe48959d44538e9697c08f34200fa5f0947d2"},
|
"jose": {:hex, :jose, "1.11.5", "3bc2d75ffa5e2c941ca93e5696b54978323191988eb8d225c2e663ddfefd515e", [:mix, :rebar3], [], "hexpm", "dcd3b215bafe02ea7c5b23dafd3eb8062a5cd8f2d904fd9caa323d37034ab384"},
|
||||||
"jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
|
"jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
|
||||||
"linkify": {:git, "https://akkoma.dev/AkkomaGang/linkify.git", "2567e2c1073fa371fd26fd66dfa5bc77b6919c16", [branch: "bugfix/line-ending-buffer"]},
|
"linkify": {:git, "https://akkoma.dev/AkkomaGang/linkify.git", "2567e2c1073fa371fd26fd66dfa5bc77b6919c16", [branch: "bugfix/line-ending-buffer"]},
|
||||||
"mail": {:hex, :mail, "0.2.3", "2c6bb5f8a5f74845fa50ecd0fb45ea16b164026f285f45104f1c4c078cd616d4", [:mix], [], "hexpm", "932b398fa9c69fdf290d7ff63175826e0f1e24414d5b0763bb00a2acfc6c6bf5"},
|
"mail": {:hex, :mail, "0.2.3", "2c6bb5f8a5f74845fa50ecd0fb45ea16b164026f285f45104f1c4c078cd616d4", [:mix], [], "hexpm", "932b398fa9c69fdf290d7ff63175826e0f1e24414d5b0763bb00a2acfc6c6bf5"},
|
||||||
|
@ -76,7 +76,7 @@
|
||||||
"mock": {:hex, :mock, "0.3.7", "75b3bbf1466d7e486ea2052a73c6e062c6256fb429d6797999ab02fa32f29e03", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4da49a4609e41fd99b7836945c26f373623ea968cfb6282742bcb94440cf7e5c"},
|
"mock": {:hex, :mock, "0.3.7", "75b3bbf1466d7e486ea2052a73c6e062c6256fb429d6797999ab02fa32f29e03", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4da49a4609e41fd99b7836945c26f373623ea968cfb6282742bcb94440cf7e5c"},
|
||||||
"mogrify": {:hex, :mogrify, "0.9.2", "b360984adea7dd6a55f18028e6327973c58de7f548fdb86c9859848aa904d5b0", [:mix], [], "hexpm", "c18d10fd70ca20e2585301616c89f6e4f7159d92efc9cc8ee579e00c886f699d"},
|
"mogrify": {:hex, :mogrify, "0.9.2", "b360984adea7dd6a55f18028e6327973c58de7f548fdb86c9859848aa904d5b0", [:mix], [], "hexpm", "c18d10fd70ca20e2585301616c89f6e4f7159d92efc9cc8ee579e00c886f699d"},
|
||||||
"mox": {:hex, :mox, "1.0.2", "dc2057289ac478b35760ba74165b4b3f402f68803dd5aecd3bfd19c183815d64", [:mix], [], "hexpm", "f9864921b3aaf763c8741b5b8e6f908f44566f1e427b2630e89e9a73b981fef2"},
|
"mox": {:hex, :mox, "1.0.2", "dc2057289ac478b35760ba74165b4b3f402f68803dd5aecd3bfd19c183815d64", [:mix], [], "hexpm", "f9864921b3aaf763c8741b5b8e6f908f44566f1e427b2630e89e9a73b981fef2"},
|
||||||
"nimble_options": {:hex, :nimble_options, "0.5.1", "5c166f7669e40333191bea38e3bd3811cc13f459f1e4be49e89128a21b5d8c4d", [:mix], [], "hexpm", "d176cf7baa4fef0ceb301ca3eb8b55bd7de3e45f489c4f8b4f2849f1f114ef3e"},
|
"nimble_options": {:hex, :nimble_options, "0.5.2", "42703307b924880f8c08d97719da7472673391905f528259915782bb346e0a1b", [:mix], [], "hexpm", "4da7f904b915fd71db549bcdc25f8d56f378ef7ae07dc1d372cbe72ba950dce0"},
|
||||||
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
|
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
|
||||||
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
|
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
|
||||||
"oban": {:hex, :oban, "2.12.1", "f604d7e6a8be9fda4a9b0f6cebbd633deba569f85dbff70c4d25d99a6f023177", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b1844c2b74e0d788b73e5144b0c9d5674cb775eae29d88a36f3c3b48d42d058"},
|
"oban": {:hex, :oban, "2.12.1", "f604d7e6a8be9fda4a9b0f6cebbd633deba569f85dbff70c4d25d99a6f023177", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b1844c2b74e0d788b73e5144b0c9d5674cb775eae29d88a36f3c3b48d42d058"},
|
||||||
|
@ -86,7 +86,7 @@
|
||||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
|
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
|
||||||
"phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"},
|
"phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"},
|
||||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
|
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
|
||||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.3", "2e3d009422addf8b15c3dccc65ce53baccbe26f7cfd21d264680b5867789a9c1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c8845177a866e017dcb7083365393c8f00ab061b8b6b2bda575891079dce81b2"},
|
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.6", "460c36977643d76fc8e0b6b3c4bba703c0ef21abc74233cf7dc15d1c1696832f", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce2768fb44c3c370df13fc4f0dc70623b662a93a201d8d7d87c4ba6542bc6b73"},
|
||||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
|
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
|
||||||
"phoenix_swoosh": {:hex, :phoenix_swoosh, "0.3.4", "615f8f393135de7e0cbb4bd00ba238b1e0cd324b0d90efbaee613c2f02ca5e5c", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.0", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "3971221846232021ab5e3c7489fd62ec5bfd6a2e01cae10a317ccf6fb350571c"},
|
"phoenix_swoosh": {:hex, :phoenix_swoosh, "0.3.4", "615f8f393135de7e0cbb4bd00ba238b1e0cd324b0d90efbaee613c2f02ca5e5c", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.0", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "3971221846232021ab5e3c7489fd62ec5bfd6a2e01cae10a317ccf6fb350571c"},
|
||||||
"phoenix_template": {:hex, :phoenix_template, "1.0.0", "c57bc5044f25f007dc86ab21895688c098a9f846a8dda6bc40e2d0ddc146e38f", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "1b066f99a26fd22064c12b2600a9a6e56700f591bf7b20b418054ea38b4d4357"},
|
"phoenix_template": {:hex, :phoenix_template, "1.0.0", "c57bc5044f25f007dc86ab21895688c098a9f846a8dda6bc40e2d0ddc146e38f", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "1b066f99a26fd22064c12b2600a9a6e56700f591bf7b20b418054ea38b4d4357"},
|
||||||
|
@ -100,14 +100,14 @@
|
||||||
"postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"},
|
"postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"},
|
||||||
"pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"},
|
"pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"},
|
||||||
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
||||||
"recon": {:hex, :recon, "2.5.2", "cba53fa8db83ad968c9a652e09c3ed7ddcc4da434f27c3eaa9ca47ffb2b1ff03", [:mix, :rebar3], [], "hexpm", "2c7523c8dee91dff41f6b3d63cba2bd49eb6d2fe5bf1eec0df7f87eb5e230e1c"},
|
"recon": {:hex, :recon, "2.5.3", "739107b9050ea683c30e96de050bc59248fd27ec147696f79a8797ff9fa17153", [:mix, :rebar3], [], "hexpm", "6c6683f46fd4a1dfd98404b9f78dcabc7fcd8826613a89dcb984727a8c3099d7"},
|
||||||
"remote_ip": {:hex, :remote_ip, "1.1.0", "cb308841595d15df3f9073b7c39243a1dd6ca56e5020295cb012c76fbec50f2d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "616ffdf66aaad6a72fc546dabf42eed87e2a99e97b09cbd92b10cc180d02ed74"},
|
"remote_ip": {:hex, :remote_ip, "1.1.0", "cb308841595d15df3f9073b7c39243a1dd6ca56e5020295cb012c76fbec50f2d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "616ffdf66aaad6a72fc546dabf42eed87e2a99e97b09cbd92b10cc180d02ed74"},
|
||||||
"search_parser": {:git, "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git", "08971a81e68686f9ac465cfb6661d51c5e4e1e7f", [ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"]},
|
"search_parser": {:git, "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git", "08971a81e68686f9ac465cfb6661d51c5e4e1e7f", [ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"]},
|
||||||
"sleeplocks": {:hex, :sleeplocks, "1.1.2", "d45aa1c5513da48c888715e3381211c859af34bee9b8290490e10c90bb6ff0ca", [:rebar3], [], "hexpm", "9fe5d048c5b781d6305c1a3a0f40bb3dfc06f49bf40571f3d2d0c57eaa7f59a5"},
|
"sleeplocks": {:hex, :sleeplocks, "1.1.2", "d45aa1c5513da48c888715e3381211c859af34bee9b8290490e10c90bb6ff0ca", [:rebar3], [], "hexpm", "9fe5d048c5b781d6305c1a3a0f40bb3dfc06f49bf40571f3d2d0c57eaa7f59a5"},
|
||||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
||||||
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
|
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
|
||||||
"sweet_xml": {:hex, :sweet_xml, "0.7.3", "debb256781c75ff6a8c5cbf7981146312b66f044a2898f453709a53e5031b45b", [:mix], [], "hexpm", "e110c867a1b3fe74bfc7dd9893aa851f0eed5518d0d7cad76d7baafd30e4f5ba"},
|
"sweet_xml": {:hex, :sweet_xml, "0.7.3", "debb256781c75ff6a8c5cbf7981146312b66f044a2898f453709a53e5031b45b", [:mix], [], "hexpm", "e110c867a1b3fe74bfc7dd9893aa851f0eed5518d0d7cad76d7baafd30e4f5ba"},
|
||||||
"swoosh": {:hex, :swoosh, "1.8.3", "733357d9a65da19c162171f08d1e42a6259236cf44d02a64711b776afbbbaa78", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c699abbac7a296c205055a7501c5d5261320ea1f08bde2392699a9e899815bc7"},
|
"swoosh": {:hex, :swoosh, "1.9.1", "0a5d7bf9954eb41d7e55525bc0940379982b090abbaef67cd8e1fd2ed7f8ca1a", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76dffff3ffcab80f249d5937a592eaef7cc49ac6f4cdd27e622868326ed6371e"},
|
||||||
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
|
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
|
||||||
"table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"},
|
"table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"},
|
||||||
"telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
|
"telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
|
||||||
|
|
1
priv/language-codes.json
Normal file
1
priv/language-codes.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -41,7 +41,11 @@
|
||||||
"@type": "@id"
|
"@type": "@id"
|
||||||
},
|
},
|
||||||
"vcard": "http://www.w3.org/2006/vcard/ns#",
|
"vcard": "http://www.w3.org/2006/vcard/ns#",
|
||||||
"formerRepresentations": "litepub:formerRepresentations"
|
"formerRepresentations": "litepub:formerRepresentations",
|
||||||
|
"contentMap": {
|
||||||
|
"@id": "as:content",
|
||||||
|
"@container": "@language"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
38
test/fixtures/mastodon/note_with_language.json
vendored
Normal file
38
test/fixtures/mastodon/note_with_language.json
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
{
|
||||||
|
"ostatus": "http://ostatus.org#",
|
||||||
|
"atomUri": "ostatus:atomUri",
|
||||||
|
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||||
|
"conversation": "ostatus:conversation",
|
||||||
|
"sensitive": "as:sensitive",
|
||||||
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
|
"votersCount": "toot:votersCount"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "https://mastodon.social/users/akkoma_ap_integration_tester/statuses/109671288784583764",
|
||||||
|
"type": "Note",
|
||||||
|
"summary": null,
|
||||||
|
"inReplyTo": null,
|
||||||
|
"published": "2023-01-11T15:31:01Z",
|
||||||
|
"url": "https://mastodon.social/@akkoma_ap_integration_tester/109671288784583764",
|
||||||
|
"attributedTo": "https://mastodon.social/users/akkoma_ap_integration_tester",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [
|
||||||
|
"https://mastodon.social/users/akkoma_ap_integration_tester/followers"
|
||||||
|
],
|
||||||
|
"sensitive": false,
|
||||||
|
"atomUri": "https://mastodon.social/users/akkoma_ap_integration_tester/statuses/109671288784583764",
|
||||||
|
"inReplyToAtomUri": null,
|
||||||
|
"conversation": "tag:mastodon.social,2023-01-11:objectId=376794415:objectType=Conversation",
|
||||||
|
"content": "<p>tag</p>",
|
||||||
|
"contentMap": {
|
||||||
|
"ja": "<p>tag</p>"
|
||||||
|
},
|
||||||
|
"attachment": [],
|
||||||
|
"tag": [],
|
||||||
|
"replies": []
|
||||||
|
}
|
BIN
test/instance_static/emoji/test with spaces/blank.png
Normal file
BIN
test/instance_static/emoji/test with spaces/blank.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 95 B |
BIN
test/instance_static/emoji/test with spaces/blank2.png
Normal file
BIN
test/instance_static/emoji/test with spaces/blank2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 95 B |
12
test/instance_static/emoji/test with spaces/pack.json
Normal file
12
test/instance_static/emoji/test with spaces/pack.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"files": {
|
||||||
|
"blank": "blank.png",
|
||||||
|
"blank2": "blank2.png"
|
||||||
|
},
|
||||||
|
"pack": {
|
||||||
|
"description": "Test description",
|
||||||
|
"homepage": "https://pleroma.social",
|
||||||
|
"license": "Test license",
|
||||||
|
"share-files": true
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,7 +46,6 @@ test "it replaces objects with references" do
|
||||||
|
|
||||||
describe "prune_objects" do
|
describe "prune_objects" do
|
||||||
test "it prunes old objects from the database" do
|
test "it prunes old objects from the database" do
|
||||||
insert(:note)
|
|
||||||
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days]) + 1
|
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days]) + 1
|
||||||
|
|
||||||
date =
|
date =
|
||||||
|
@ -55,18 +54,304 @@ test "it prunes old objects from the database" do
|
||||||
|> Timex.to_naive_datetime()
|
|> Timex.to_naive_datetime()
|
||||||
|> NaiveDateTime.truncate(:second)
|
|> NaiveDateTime.truncate(:second)
|
||||||
|
|
||||||
%{id: id} =
|
insert(:note)
|
||||||
|
|
||||||
|
%{id: note_remote_public_id} =
|
||||||
:note
|
:note
|
||||||
|> insert()
|
|> insert()
|
||||||
|> Ecto.Changeset.change(%{inserted_at: date})
|
|> Ecto.Changeset.change(%{updated_at: date})
|
||||||
|> Repo.update!()
|
|> Repo.update!()
|
||||||
|
|
||||||
assert length(Repo.all(Object)) == 2
|
note_remote_non_public =
|
||||||
|
%{id: note_remote_non_public_id, data: note_remote_non_public_data} =
|
||||||
|
:note
|
||||||
|
|> insert()
|
||||||
|
|
||||||
|
note_remote_non_public
|
||||||
|
|> Ecto.Changeset.change(%{
|
||||||
|
updated_at: date,
|
||||||
|
data: note_remote_non_public_data |> update_in(["to"], fn _ -> [] end)
|
||||||
|
})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
assert length(Repo.all(Object)) == 3
|
||||||
|
|
||||||
Mix.Tasks.Pleroma.Database.run(["prune_objects"])
|
Mix.Tasks.Pleroma.Database.run(["prune_objects"])
|
||||||
|
|
||||||
assert length(Repo.all(Object)) == 1
|
assert length(Repo.all(Object)) == 1
|
||||||
refute Object.get_by_id(id)
|
refute Object.get_by_id(note_remote_public_id)
|
||||||
|
refute Object.get_by_id(note_remote_non_public_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with the --keep-non-public option it still keeps non-public posts even if they are not local" do
|
||||||
|
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days]) + 1
|
||||||
|
|
||||||
|
date =
|
||||||
|
Timex.now()
|
||||||
|
|> Timex.shift(days: -deadline)
|
||||||
|
|> Timex.to_naive_datetime()
|
||||||
|
|> NaiveDateTime.truncate(:second)
|
||||||
|
|
||||||
|
insert(:note)
|
||||||
|
|
||||||
|
%{id: note_remote_id} =
|
||||||
|
:note
|
||||||
|
|> insert()
|
||||||
|
|> Ecto.Changeset.change(%{updated_at: date})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
note_remote_non_public =
|
||||||
|
%{data: note_remote_non_public_data} =
|
||||||
|
:note
|
||||||
|
|> insert()
|
||||||
|
|
||||||
|
note_remote_non_public
|
||||||
|
|> Ecto.Changeset.change(%{
|
||||||
|
updated_at: date,
|
||||||
|
data: note_remote_non_public_data |> update_in(["to"], fn _ -> [] end)
|
||||||
|
})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
assert length(Repo.all(Object)) == 3
|
||||||
|
|
||||||
|
Mix.Tasks.Pleroma.Database.run(["prune_objects", "--keep-non-public"])
|
||||||
|
|
||||||
|
assert length(Repo.all(Object)) == 2
|
||||||
|
refute Object.get_by_id(note_remote_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with the --keep-threads and --keep-non-public option it keeps old threads with non-public replies even if the interaction is not local" do
|
||||||
|
# For non-public we only check Create Activities because only these are relevant for threads
|
||||||
|
# Flags are always non-public, Announces from relays can be non-public...
|
||||||
|
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days]) + 1
|
||||||
|
|
||||||
|
old_insert_date =
|
||||||
|
Timex.now()
|
||||||
|
|> Timex.shift(days: -deadline)
|
||||||
|
|> Timex.to_naive_datetime()
|
||||||
|
|> NaiveDateTime.truncate(:second)
|
||||||
|
|
||||||
|
remote_user1 = insert(:user, local: false)
|
||||||
|
remote_user2 = insert(:user, local: false)
|
||||||
|
|
||||||
|
# Old remote non-public reply (should be kept)
|
||||||
|
{:ok, old_remote_post1_activity} =
|
||||||
|
CommonAPI.post(remote_user1, %{status: "some thing", local: false})
|
||||||
|
|
||||||
|
old_remote_post1_activity
|
||||||
|
|> Ecto.Changeset.change(%{local: false, updated_at: old_insert_date})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
{:ok, old_remote_non_public_reply_activity} =
|
||||||
|
CommonAPI.post(remote_user2, %{
|
||||||
|
status: "some reply",
|
||||||
|
in_reply_to_status_id: old_remote_post1_activity.id
|
||||||
|
})
|
||||||
|
|
||||||
|
old_remote_non_public_reply_activity
|
||||||
|
|> Ecto.Changeset.change(%{
|
||||||
|
local: false,
|
||||||
|
updated_at: old_insert_date,
|
||||||
|
data: old_remote_non_public_reply_activity.data |> update_in(["to"], fn _ -> [] end)
|
||||||
|
})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
# Old remote non-public Announce (should be removed)
|
||||||
|
{:ok, old_remote_post2_activity = %{data: %{"object" => old_remote_post2_id}}} =
|
||||||
|
CommonAPI.post(remote_user1, %{status: "some thing", local: false})
|
||||||
|
|
||||||
|
old_remote_post2_activity
|
||||||
|
|> Ecto.Changeset.change(%{local: false, updated_at: old_insert_date})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
{:ok, old_remote_non_public_repeat_activity} =
|
||||||
|
CommonAPI.repeat(old_remote_post2_activity.id, remote_user2)
|
||||||
|
|
||||||
|
old_remote_non_public_repeat_activity
|
||||||
|
|> Ecto.Changeset.change(%{
|
||||||
|
local: false,
|
||||||
|
updated_at: old_insert_date,
|
||||||
|
data: old_remote_non_public_repeat_activity.data |> update_in(["to"], fn _ -> [] end)
|
||||||
|
})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
assert length(Repo.all(Object)) == 3
|
||||||
|
|
||||||
|
Mix.Tasks.Pleroma.Database.run(["prune_objects", "--keep-threads", "--keep-non-public"])
|
||||||
|
|
||||||
|
Repo.all(Pleroma.Activity)
|
||||||
|
assert length(Repo.all(Object)) == 2
|
||||||
|
refute Object.get_by_ap_id(old_remote_post2_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with the --keep-threads option it still keeps non-old threads even with no local interactions" do
|
||||||
|
remote_user = insert(:user, local: false)
|
||||||
|
remote_user2 = insert(:user, local: false)
|
||||||
|
|
||||||
|
{:ok, remote_post_activity} =
|
||||||
|
CommonAPI.post(remote_user, %{status: "some thing", local: false})
|
||||||
|
|
||||||
|
{:ok, remote_post_reply_activity} =
|
||||||
|
CommonAPI.post(remote_user2, %{
|
||||||
|
status: "some reply",
|
||||||
|
in_reply_to_status_id: remote_post_activity.id
|
||||||
|
})
|
||||||
|
|
||||||
|
remote_post_activity
|
||||||
|
|> Ecto.Changeset.change(%{local: false})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
remote_post_reply_activity
|
||||||
|
|> Ecto.Changeset.change(%{local: false})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
assert length(Repo.all(Object)) == 2
|
||||||
|
|
||||||
|
Mix.Tasks.Pleroma.Database.run(["prune_objects", "--keep-threads"])
|
||||||
|
|
||||||
|
assert length(Repo.all(Object)) == 2
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with the --keep-threads option it deletes old threads with no local interaction" do
|
||||||
|
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days]) + 1
|
||||||
|
|
||||||
|
old_insert_date =
|
||||||
|
Timex.now()
|
||||||
|
|> Timex.shift(days: -deadline)
|
||||||
|
|> Timex.to_naive_datetime()
|
||||||
|
|> NaiveDateTime.truncate(:second)
|
||||||
|
|
||||||
|
remote_user = insert(:user, local: false)
|
||||||
|
remote_user2 = insert(:user, local: false)
|
||||||
|
|
||||||
|
{:ok, old_remote_post_activity} =
|
||||||
|
CommonAPI.post(remote_user, %{status: "some thing", local: false})
|
||||||
|
|
||||||
|
old_remote_post_activity
|
||||||
|
|> Ecto.Changeset.change(%{local: false, updated_at: old_insert_date})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
{:ok, old_remote_post_reply_activity} =
|
||||||
|
CommonAPI.post(remote_user2, %{
|
||||||
|
status: "some reply",
|
||||||
|
in_reply_to_status_id: old_remote_post_activity.id
|
||||||
|
})
|
||||||
|
|
||||||
|
old_remote_post_reply_activity
|
||||||
|
|> Ecto.Changeset.change(%{local: false, updated_at: old_insert_date})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
{:ok, old_favourite_activity} =
|
||||||
|
CommonAPI.favorite(remote_user2, old_remote_post_activity.id)
|
||||||
|
|
||||||
|
old_favourite_activity
|
||||||
|
|> Ecto.Changeset.change(%{local: false, updated_at: old_insert_date})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
{:ok, old_repeat_activity} = CommonAPI.repeat(old_remote_post_activity.id, remote_user2)
|
||||||
|
|
||||||
|
old_repeat_activity
|
||||||
|
|> Ecto.Changeset.change(%{local: false, updated_at: old_insert_date})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
assert length(Repo.all(Object)) == 2
|
||||||
|
|
||||||
|
Mix.Tasks.Pleroma.Database.run(["prune_objects", "--keep-threads"])
|
||||||
|
|
||||||
|
assert length(Repo.all(Object)) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with the --keep-threads option it keeps old threads with local interaction" do
|
||||||
|
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days]) + 1
|
||||||
|
|
||||||
|
old_insert_date =
|
||||||
|
Timex.now()
|
||||||
|
|> Timex.shift(days: -deadline)
|
||||||
|
|> Timex.to_naive_datetime()
|
||||||
|
|> NaiveDateTime.truncate(:second)
|
||||||
|
|
||||||
|
remote_user = insert(:user, local: false)
|
||||||
|
local_user = insert(:user, local: true)
|
||||||
|
|
||||||
|
# local reply
|
||||||
|
{:ok, old_remote_post1_activity} =
|
||||||
|
CommonAPI.post(remote_user, %{status: "some thing", local: false})
|
||||||
|
|
||||||
|
old_remote_post1_activity
|
||||||
|
|> Ecto.Changeset.change(%{local: false, updated_at: old_insert_date})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
{:ok, old_local_post2_reply_activity} =
|
||||||
|
CommonAPI.post(local_user, %{
|
||||||
|
status: "some reply",
|
||||||
|
in_reply_to_status_id: old_remote_post1_activity.id
|
||||||
|
})
|
||||||
|
|
||||||
|
old_local_post2_reply_activity
|
||||||
|
|> Ecto.Changeset.change(%{local: true, updated_at: old_insert_date})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
# local Like
|
||||||
|
{:ok, old_remote_post3_activity} =
|
||||||
|
CommonAPI.post(remote_user, %{status: "some thing", local: false})
|
||||||
|
|
||||||
|
old_remote_post3_activity
|
||||||
|
|> Ecto.Changeset.change(%{local: false, updated_at: old_insert_date})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
{:ok, old_favourite_activity} = CommonAPI.favorite(local_user, old_remote_post3_activity.id)
|
||||||
|
|
||||||
|
old_favourite_activity
|
||||||
|
|> Ecto.Changeset.change(%{local: true, updated_at: old_insert_date})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
# local Announce
|
||||||
|
{:ok, old_remote_post4_activity} =
|
||||||
|
CommonAPI.post(remote_user, %{status: "some thing", local: false})
|
||||||
|
|
||||||
|
old_remote_post4_activity
|
||||||
|
|> Ecto.Changeset.change(%{local: false, updated_at: old_insert_date})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
{:ok, old_repeat_activity} = CommonAPI.repeat(old_remote_post4_activity.id, local_user)
|
||||||
|
|
||||||
|
old_repeat_activity
|
||||||
|
|> Ecto.Changeset.change(%{local: true, updated_at: old_insert_date})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
assert length(Repo.all(Object)) == 4
|
||||||
|
|
||||||
|
Mix.Tasks.Pleroma.Database.run(["prune_objects", "--keep-threads"])
|
||||||
|
|
||||||
|
assert length(Repo.all(Object)) == 4
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with the --keep-threads option it keeps old threads with bookmarked posts" do
|
||||||
|
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days]) + 1
|
||||||
|
|
||||||
|
old_insert_date =
|
||||||
|
Timex.now()
|
||||||
|
|> Timex.shift(days: -deadline)
|
||||||
|
|> Timex.to_naive_datetime()
|
||||||
|
|> NaiveDateTime.truncate(:second)
|
||||||
|
|
||||||
|
remote_user = insert(:user, local: false)
|
||||||
|
local_user = insert(:user, local: true)
|
||||||
|
|
||||||
|
{:ok, old_remote_post_activity} =
|
||||||
|
CommonAPI.post(remote_user, %{status: "some thing", local: false})
|
||||||
|
|
||||||
|
old_remote_post_activity
|
||||||
|
|> Ecto.Changeset.change(%{local: false, updated_at: old_insert_date})
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
Pleroma.Bookmark.create(local_user.id, old_remote_post_activity.id)
|
||||||
|
|
||||||
|
assert length(Repo.all(Object)) == 1
|
||||||
|
|
||||||
|
Mix.Tasks.Pleroma.Database.run(["prune_objects", "--keep-threads"])
|
||||||
|
|
||||||
|
assert length(Repo.all(Object)) == 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
93
test/pleroma/akkoma/translators/argos_translate_test.exs
Normal file
93
test/pleroma/akkoma/translators/argos_translate_test.exs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
defmodule Pleroma.Akkoma.Translators.ArgosTranslateTest do
|
||||||
|
alias Pleroma.Akkoma.Translators.ArgosTranslate
|
||||||
|
|
||||||
|
import Mock
|
||||||
|
|
||||||
|
use Pleroma.DataCase, async: true
|
||||||
|
|
||||||
|
setup do
|
||||||
|
clear_config([:argos_translate, :command_argos_translate], "argos-translate_test")
|
||||||
|
clear_config([:argos_translate, :command_argospm], "argospm_test")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it lists available languages" do
|
||||||
|
languages =
|
||||||
|
with_mock System, [:passthrough],
|
||||||
|
cmd: fn "argospm_test", ["list"], _ ->
|
||||||
|
{"translate-nl_en\ntranslate-en_nl\ntranslate-ja_en\n", 0}
|
||||||
|
end do
|
||||||
|
ArgosTranslate.languages()
|
||||||
|
end
|
||||||
|
|
||||||
|
assert {:ok, source_langs, dest_langs} = languages
|
||||||
|
|
||||||
|
assert [%{code: "en", name: "en"}, %{code: "ja", name: "ja"}, %{code: "nl", name: "nl"}] =
|
||||||
|
source_langs |> Enum.sort()
|
||||||
|
|
||||||
|
assert [%{code: "en", name: "en"}, %{code: "nl", name: "nl"}] = dest_langs |> Enum.sort()
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it translates from the to language when no language is set and returns the text unchanged" do
|
||||||
|
assert {:ok, "nl", "blabla"} = ArgosTranslate.translate("blabla", nil, "nl")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it translates from the provided language if provided" do
|
||||||
|
translation_response =
|
||||||
|
with_mock System, [:passthrough],
|
||||||
|
cmd: fn "argos-translate_test", ["--from-lang", "nl", "--to-lang", "en", "blabla"], _ ->
|
||||||
|
{"yadayada", 0}
|
||||||
|
end do
|
||||||
|
ArgosTranslate.translate("blabla", "nl", "en")
|
||||||
|
end
|
||||||
|
|
||||||
|
assert {:ok, "nl", "yadayada"} = translation_response
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns a proper error when the executable can't be found" do
|
||||||
|
non_existing_command = "sfqsfgqsefd"
|
||||||
|
clear_config([:argos_translate, :command_argos_translate], non_existing_command)
|
||||||
|
clear_config([:argos_translate, :command_argospm], non_existing_command)
|
||||||
|
|
||||||
|
assert nil == System.find_executable(non_existing_command)
|
||||||
|
|
||||||
|
assert {:error, "ArgosTranslate failed to fetch languages" <> _} = ArgosTranslate.languages()
|
||||||
|
|
||||||
|
assert {:error, "ArgosTranslate failed to translate" <> _} =
|
||||||
|
ArgosTranslate.translate("blabla", "nl", "en")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it can strip html" do
|
||||||
|
content =
|
||||||
|
~s[<p>What's up my fellow fedizens?</p><p>So anyway</p><ul><li><a class="hashtag" data-tag="cofe" href="https://suya.space/tag/cofe">#cofe</a></li><li><a class="hashtag" data-tag="suya" href="https://cofe.space/tag/suya">#Suya</a></li></ul><p>ammiright!<br/>:ablobfoxhyper:</p>]
|
||||||
|
|
||||||
|
stripped_content =
|
||||||
|
"\nWhat's up my fellow fedizens?\n\nSo anyway\n\n#cofe\n#Suya\nammiright!\n:ablobfoxhyper:\n"
|
||||||
|
|
||||||
|
expected_response_strip_html =
|
||||||
|
"<br/>What's up my fellow fedizens?<br/><br/>So anyway<br/><br/>#cofe<br/>#Suya<br/>ammiright!<br/>:ablobfoxhyper:<br/>"
|
||||||
|
|
||||||
|
response_strip_html =
|
||||||
|
with_mock System, [:passthrough],
|
||||||
|
cmd: fn "argos-translate_test",
|
||||||
|
["--from-lang", _, "--to-lang", _, ^stripped_content],
|
||||||
|
_ ->
|
||||||
|
{stripped_content, 0}
|
||||||
|
end do
|
||||||
|
ArgosTranslate.translate(content, "nl", "en")
|
||||||
|
end
|
||||||
|
|
||||||
|
clear_config([:argos_translate, :strip_html], false)
|
||||||
|
|
||||||
|
response_no_strip_html =
|
||||||
|
with_mock System, [:passthrough],
|
||||||
|
cmd: fn "argos-translate_test", ["--from-lang", _, "--to-lang", _, string], _ ->
|
||||||
|
{string, 0}
|
||||||
|
end do
|
||||||
|
ArgosTranslate.translate(content, "nl", "en")
|
||||||
|
end
|
||||||
|
|
||||||
|
assert {:ok, "nl", content} == response_no_strip_html
|
||||||
|
|
||||||
|
assert {:ok, "nl", expected_response_strip_html} == response_strip_html
|
||||||
|
end
|
||||||
|
end
|
|
@ -227,6 +227,10 @@ test "pleroma module" do
|
||||||
assert ConfigDB.to_elixir_types("Pleroma.Bookmark") == Pleroma.Bookmark
|
assert ConfigDB.to_elixir_types("Pleroma.Bookmark") == Pleroma.Bookmark
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "removed module" do
|
||||||
|
assert ConfigDB.to_elixir_types("Pleroma.Nowhere") == :invalid_atom
|
||||||
|
end
|
||||||
|
|
||||||
test "pleroma string" do
|
test "pleroma string" do
|
||||||
assert ConfigDB.to_elixir_types("Pleroma") == "Pleroma"
|
assert ConfigDB.to_elixir_types("Pleroma") == "Pleroma"
|
||||||
end
|
end
|
||||||
|
|
11
test/pleroma/iso639_test.exs
Normal file
11
test/pleroma/iso639_test.exs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
defmodule Pleroma.ISO639Test do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
describe "ISO639 validation" do
|
||||||
|
test "should validate a language" do
|
||||||
|
assert Pleroma.ISO639.valid_alpha2?("en")
|
||||||
|
assert Pleroma.ISO639.valid_alpha2?("ja")
|
||||||
|
refute Pleroma.ISO639.valid_alpha2?("xx")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -30,8 +30,8 @@ test "returns backup codes" do
|
||||||
{:ok, [code1, code2]} = MFA.generate_backup_codes(user)
|
{:ok, [code1, code2]} = MFA.generate_backup_codes(user)
|
||||||
updated_user = refresh_record(user)
|
updated_user = refresh_record(user)
|
||||||
[hash1, hash2] = updated_user.multi_factor_authentication_settings.backup_codes
|
[hash1, hash2] = updated_user.multi_factor_authentication_settings.backup_codes
|
||||||
assert Pleroma.Password.Pbkdf2.verify_pass(code1, hash1)
|
assert Pleroma.Password.checkpw(code1, hash1)
|
||||||
assert Pleroma.Password.Pbkdf2.verify_pass(code2, hash2)
|
assert Pleroma.Password.checkpw(code2, hash2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
65
test/pleroma/password_test.exs
Normal file
65
test/pleroma/password_test.exs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
defmodule Pleroma.PasswordTest do
|
||||||
|
use Pleroma.DataCase, async: true
|
||||||
|
import Pleroma.Factory
|
||||||
|
import ExUnit.CaptureLog
|
||||||
|
|
||||||
|
alias Pleroma.Password
|
||||||
|
|
||||||
|
describe "hash_pwd_salt/1" do
|
||||||
|
test "returns a hash" do
|
||||||
|
assert "$argon2id" <> _ = Password.hash_pwd_salt("test")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "maybe_update_password/2" do
|
||||||
|
test "with a bcrypt hash, it updates to an argon2 hash" do
|
||||||
|
user = insert(:user, password_hash: Bcrypt.hash_pwd_salt("123"))
|
||||||
|
assert "$2" <> _ = user.password_hash
|
||||||
|
|
||||||
|
{:ok, user} = Password.maybe_update_password(user, "123")
|
||||||
|
assert "$argon2" <> _ = user.password_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with a pbkdf2 hash, it updates to an argon2 hash" do
|
||||||
|
user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("123"))
|
||||||
|
assert "$pbkdf2" <> _ = user.password_hash
|
||||||
|
|
||||||
|
{:ok, user} = Password.maybe_update_password(user, "123")
|
||||||
|
assert "$argon2" <> _ = user.password_hash
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "checkpw/2" do
|
||||||
|
test "check pbkdf2 hash" do
|
||||||
|
hash =
|
||||||
|
"$pbkdf2-sha512$160000$loXqbp8GYls43F0i6lEfIw$AY.Ep.2pGe57j2hAPY635sI/6w7l9Q9u9Bp02PkPmF3OrClDtJAI8bCiivPr53OKMF7ph6iHhN68Rom5nEfC2A"
|
||||||
|
|
||||||
|
assert Password.checkpw("test-password", hash)
|
||||||
|
refute Password.checkpw("test-password1", hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "check bcrypt hash" do
|
||||||
|
hash = "$2a$10$uyhC/R/zoE1ndwwCtMusK.TLVzkQ/Ugsbqp3uXI.CTTz0gBw.24jS"
|
||||||
|
|
||||||
|
assert Password.checkpw("password", hash)
|
||||||
|
refute Password.checkpw("password1", hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "check argon2 hash" do
|
||||||
|
hash =
|
||||||
|
"$argon2id$v=19$m=65536,t=8,p=2$zEMMsTuK5KkL5AFWbX7jyQ$VyaQD7PF6e9btz0oH1YiAkWwIGZ7WNDZP8l+a/O171g"
|
||||||
|
|
||||||
|
assert Password.checkpw("password", hash)
|
||||||
|
refute Password.checkpw("password1", hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns false when hash invalid" do
|
||||||
|
hash =
|
||||||
|
"psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
|
||||||
|
|
||||||
|
assert capture_log(fn ->
|
||||||
|
refute Password.checkpw("password", hash)
|
||||||
|
end) =~ "[error] Password hash not recognized"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -48,6 +48,31 @@ test "wildcard domains with two subdomains" do
|
||||||
refute MRF.subdomain_match?(regexes, "sub.unsafe.tldanother")
|
refute MRF.subdomain_match?(regexes, "sub.unsafe.tldanother")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "wildcard on the tld" do
|
||||||
|
regexes = MRF.subdomains_regex(["somewhere.*"])
|
||||||
|
|
||||||
|
assert regexes == [~r/^(.+\.)?somewhere\.(.+)$/i]
|
||||||
|
|
||||||
|
assert MRF.subdomain_match?(regexes, "somewhere.net")
|
||||||
|
assert MRF.subdomain_match?(regexes, "somewhere.com")
|
||||||
|
assert MRF.subdomain_match?(regexes, "somewhere.somewherelese.net")
|
||||||
|
refute MRF.subdomain_match?(regexes, "somewhere")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "wildcards on subdomain _and_ tld" do
|
||||||
|
regexes = MRF.subdomains_regex(["*.somewhere.*"])
|
||||||
|
|
||||||
|
assert regexes == [~r/^(.+\.)?somewhere\.(.+)$/i]
|
||||||
|
|
||||||
|
assert MRF.subdomain_match?(regexes, "somewhere.net")
|
||||||
|
assert MRF.subdomain_match?(regexes, "somewhere.com")
|
||||||
|
assert MRF.subdomain_match?(regexes, "sub.somewhere.net")
|
||||||
|
assert MRF.subdomain_match?(regexes, "sub.somewhere.com")
|
||||||
|
assert MRF.subdomain_match?(regexes, "sub.sub.somewhere.net")
|
||||||
|
assert MRF.subdomain_match?(regexes, "sub.sub.somewhere.com")
|
||||||
|
refute MRF.subdomain_match?(regexes, "somewhere")
|
||||||
|
end
|
||||||
|
|
||||||
test "matches are case-insensitive" do
|
test "matches are case-insensitive" do
|
||||||
regexes = MRF.subdomains_regex(["UnSafe.TLD", "UnSAFE2.Tld"])
|
regexes = MRF.subdomains_regex(["UnSafe.TLD", "UnSAFE2.Tld"])
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,20 @@ test "a basic note validates", %{note: note} do
|
||||||
%{valid?: true} = ArticleNotePageValidator.cast_and_validate(note)
|
%{valid?: true} = ArticleNotePageValidator.cast_and_validate(note)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "a note with a language validates" do
|
||||||
|
insert(:user, %{ap_id: "https://mastodon.social/users/akkoma_ap_integration_tester"})
|
||||||
|
note = File.read!("test/fixtures/mastodon/note_with_language.json") |> Jason.decode!()
|
||||||
|
|
||||||
|
%{
|
||||||
|
valid?: true,
|
||||||
|
changes: %{
|
||||||
|
contentMap: %{
|
||||||
|
"ja" => "<p>tag</p>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} = ArticleNotePageValidator.cast_and_validate(note)
|
||||||
|
end
|
||||||
|
|
||||||
test "a note from factory validates" do
|
test "a note from factory validates" do
|
||||||
note = insert(:note)
|
note = insert(:note)
|
||||||
%{valid?: true} = ArticleNotePageValidator.cast_and_validate(note.data)
|
%{valid?: true} = ArticleNotePageValidator.cast_and_validate(note.data)
|
||||||
|
|
|
@ -5,6 +5,8 @@ defmodule Pleroma.Web.AkkomaAPI.MetricsControllerTest do
|
||||||
test "should return metrics when the user has admin:metrics" do
|
test "should return metrics when the user has admin:metrics" do
|
||||||
%{conn: conn} = oauth_access(["admin:metrics"])
|
%{conn: conn} = oauth_access(["admin:metrics"])
|
||||||
|
|
||||||
|
Pleroma.PrometheusExporter.gather()
|
||||||
|
|
||||||
resp =
|
resp =
|
||||||
conn
|
conn
|
||||||
|> get("/api/v1/akkoma/metrics")
|
|> get("/api/v1/akkoma/metrics")
|
||||||
|
|
|
@ -11,7 +11,7 @@ test "with HTTP Basic Auth used, grants access to OAuth scope-restricted endpoin
|
||||||
conn: conn
|
conn: conn
|
||||||
} do
|
} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
assert Pleroma.Password.Pbkdf2.verify_pass("test", user.password_hash)
|
assert Pleroma.Password.checkpw("test", user.password_hash)
|
||||||
|
|
||||||
basic_auth_contents =
|
basic_auth_contents =
|
||||||
(URI.encode_www_form(user.nickname) <> ":" <> URI.encode_www_form("test"))
|
(URI.encode_www_form(user.nickname) <> ":" <> URI.encode_www_form("test"))
|
||||||
|
|
|
@ -15,7 +15,7 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticatorTest do
|
||||||
user =
|
user =
|
||||||
insert(:user,
|
insert(:user,
|
||||||
nickname: name,
|
nickname: name,
|
||||||
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password)
|
password_hash: Pleroma.Password.hash_pwd_salt(password)
|
||||||
)
|
)
|
||||||
|
|
||||||
{:ok, [user: user, name: name, password: password]}
|
{:ok, [user: user, name: name, password: password]}
|
||||||
|
@ -30,7 +30,7 @@ test "get_user/authorization", %{name: name, password: password} do
|
||||||
|
|
||||||
assert {:ok, returned_user} = res
|
assert {:ok, returned_user} = res
|
||||||
assert returned_user.id == user.id
|
assert returned_user.id == user.id
|
||||||
assert "$pbkdf2" <> _ = returned_user.password_hash
|
assert "$argon2" <> _ = returned_user.password_hash
|
||||||
end
|
end
|
||||||
|
|
||||||
test "get_user/authorization with invalid password", %{name: name} do
|
test "get_user/authorization with invalid password", %{name: name} do
|
||||||
|
|
|
@ -34,7 +34,7 @@ test "checks backup codes" do
|
||||||
|
|
||||||
hashed_codes =
|
hashed_codes =
|
||||||
backup_codes
|
backup_codes
|
||||||
|> Enum.map(&Pleroma.Password.Pbkdf2.hash_pwd_salt(&1))
|
|> Enum.map(&Pleroma.Password.hash_pwd_salt(&1))
|
||||||
|
|
||||||
user =
|
user =
|
||||||
insert(:user,
|
insert(:user,
|
||||||
|
|
|
@ -495,8 +495,16 @@ test "removes microseconds from date (String)" do
|
||||||
assert Utils.to_masto_date("2015-01-23T23:50:07.123Z") == "2015-01-23T23:50:07.000Z"
|
assert Utils.to_masto_date("2015-01-23T23:50:07.123Z") == "2015-01-23T23:50:07.000Z"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns empty string when date invalid" do
|
test "returns unix epoch when date invalid" do
|
||||||
assert Utils.to_masto_date("2015-01?23T23:50:07.123Z") == ""
|
assert Utils.to_masto_date("2015-01?23T23:50:07.123Z") == "1970-01-01T00:00:00Z"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns unix epoch when date is before the introduction of the Gregorian Calendar" do
|
||||||
|
assert Utils.to_masto_date("0621-01-01T00:00:00Z") == "1970-01-01T00:00:00Z"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns unix epoch when date is BCE" do
|
||||||
|
assert Utils.to_masto_date("-0420-01-01T00:00:00Z") == "1970-01-01T00:00:00Z"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1919,6 +1919,50 @@ test "account lookup", %{conn: conn} do
|
||||||
|> json_response_and_validate_schema(404)
|
|> json_response_and_validate_schema(404)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "account lookup with restrict unauthenticated profiles for local" do
|
||||||
|
clear_config([:restrict_unauthenticated, :profiles, :local], true)
|
||||||
|
|
||||||
|
user = insert(:user, local: true)
|
||||||
|
reading_user = insert(:user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> get("/api/v1/accounts/lookup?acct=#{user.nickname}")
|
||||||
|
|
||||||
|
assert json_response_and_validate_schema(conn, 401)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, reading_user)
|
||||||
|
|> assign(:token, insert(:oauth_token, user: reading_user, scopes: ["read:accounts"]))
|
||||||
|
|> get("/api/v1/accounts/lookup?acct=#{user.nickname}")
|
||||||
|
|
||||||
|
assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
|
||||||
|
assert id == user.id
|
||||||
|
end
|
||||||
|
|
||||||
|
test "account lookup with restrict unauthenticated profiles for remote" do
|
||||||
|
clear_config([:restrict_unauthenticated, :profiles, :remote], true)
|
||||||
|
|
||||||
|
user = insert(:user, nickname: "user@example.com", local: false)
|
||||||
|
reading_user = insert(:user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> get("/api/v1/accounts/lookup?acct=#{user.nickname}")
|
||||||
|
|
||||||
|
assert json_response_and_validate_schema(conn, 401)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, reading_user)
|
||||||
|
|> assign(:token, insert(:oauth_token, user: reading_user, scopes: ["read:accounts"]))
|
||||||
|
|> get("/api/v1/accounts/lookup?acct=#{user.nickname}")
|
||||||
|
|
||||||
|
assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
|
||||||
|
assert id == user.id
|
||||||
|
end
|
||||||
|
|
||||||
test "create a note on a user" do
|
test "create a note on a user" do
|
||||||
%{conn: conn} = oauth_access(["write:accounts", "read:follows"])
|
%{conn: conn} = oauth_access(["write:accounts", "read:follows"])
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
|
@ -67,11 +67,17 @@ test "posting a status", %{conn: conn} do
|
||||||
|> post("/api/v1/statuses", %{
|
|> post("/api/v1/statuses", %{
|
||||||
"status" => "cofe",
|
"status" => "cofe",
|
||||||
"spoiler_text" => "2hu",
|
"spoiler_text" => "2hu",
|
||||||
"sensitive" => "0"
|
"sensitive" => "0",
|
||||||
|
"language" => "ja"
|
||||||
})
|
})
|
||||||
|
|
||||||
assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
|
assert %{
|
||||||
json_response_and_validate_schema(conn_one, 200)
|
"content" => "cofe",
|
||||||
|
"id" => id,
|
||||||
|
"spoiler_text" => "2hu",
|
||||||
|
"sensitive" => false,
|
||||||
|
"language" => "ja"
|
||||||
|
} = json_response_and_validate_schema(conn_one, 200)
|
||||||
|
|
||||||
assert Activity.get_by_id(id)
|
assert Activity.get_by_id(id)
|
||||||
|
|
||||||
|
@ -213,6 +219,18 @@ test "posting an undefined status with an attachment", %{user: user, conn: conn}
|
||||||
assert json_response_and_validate_schema(conn, 200)
|
assert json_response_and_validate_schema(conn, 200)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "posting a status with an invalid language", %{conn: conn} do
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"status" => "cofe",
|
||||||
|
"language" => "invalid"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"error" => "Invalid language"} = json_response_and_validate_schema(conn, 422)
|
||||||
|
end
|
||||||
|
|
||||||
test "replying to a status", %{user: user, conn: conn} do
|
test "replying to a status", %{user: user, conn: conn} do
|
||||||
{:ok, replied_to} = CommonAPI.post(user, %{status: "cofe"})
|
{:ok, replied_to} = CommonAPI.post(user, %{status: "cofe"})
|
||||||
|
|
||||||
|
@ -326,7 +344,7 @@ test "posting a fake status", %{conn: conn} do
|
||||||
test "fake statuses' preview card is not cached", %{conn: conn} do
|
test "fake statuses' preview card is not cached", %{conn: conn} do
|
||||||
clear_config([:rich_media, :enabled], true)
|
clear_config([:rich_media, :enabled], true)
|
||||||
|
|
||||||
Tesla.Mock.mock(fn
|
Tesla.Mock.mock_global(fn
|
||||||
%{
|
%{
|
||||||
method: :get,
|
method: :get,
|
||||||
url: "https://example.com/twitter-card"
|
url: "https://example.com/twitter-card"
|
||||||
|
@ -2023,6 +2041,7 @@ test "posting a quote", %{conn: conn} do
|
||||||
assert response["quote_id"] == quoted_status.id
|
assert response["quote_id"] == quoted_status.id
|
||||||
assert response["quote"]["id"] == quoted_status.id
|
assert response["quote"]["id"] == quoted_status.id
|
||||||
assert response["quote"]["content"] == quoted_status.object.data["content"]
|
assert response["quote"]["content"] == quoted_status.object.data["content"]
|
||||||
|
assert response["pleroma"]["context"] == quoted_status.data["context"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "posting a quote, quoting a status that isn't public", %{conn: conn} do
|
test "posting a quote, quoting a status that isn't public", %{conn: conn} do
|
||||||
|
|
|
@ -94,4 +94,66 @@ test "should 404 if hashtag doesn't exist" do
|
||||||
assert response["error"] == "Hashtag not found"
|
assert response["error"] == "Hashtag not found"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "GET /api/v1/followed_tags" do
|
||||||
|
test "should list followed tags" do
|
||||||
|
%{user: user, conn: conn} = oauth_access(["read:follows"])
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/followed_tags")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert Enum.empty?(response)
|
||||||
|
|
||||||
|
hashtag = insert(:hashtag, name: "jubjub")
|
||||||
|
{:ok, _user} = User.follow_hashtag(user, hashtag)
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/followed_tags")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert [%{"name" => "jubjub"}] = response
|
||||||
|
end
|
||||||
|
|
||||||
|
test "should include a link header to paginate" do
|
||||||
|
%{user: user, conn: conn} = oauth_access(["read:follows"])
|
||||||
|
|
||||||
|
for i <- 1..21 do
|
||||||
|
hashtag = insert(:hashtag, name: "jubjub#{i}}")
|
||||||
|
{:ok, _user} = User.follow_hashtag(user, hashtag)
|
||||||
|
end
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/followed_tags")
|
||||||
|
|
||||||
|
json = json_response_and_validate_schema(response, 200)
|
||||||
|
assert Enum.count(json) == 20
|
||||||
|
assert [link_header] = get_resp_header(response, "link")
|
||||||
|
assert link_header =~ "rel=\"next\""
|
||||||
|
next_link = extract_next_link_header(link_header)
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> get(next_link)
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert Enum.count(response) == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "should refuse access without read:follows scope" do
|
||||||
|
%{conn: conn} = oauth_access(["write"])
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/followed_tags")
|
||||||
|
|> json_response_and_validate_schema(403)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp extract_next_link_header(header) do
|
||||||
|
[_, next_link] = Regex.run(~r{<(?<next_link>.*)>; rel="next"}, header)
|
||||||
|
next_link
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -465,6 +465,69 @@ test "update fields", %{conn: conn} do
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "update fields with a link to content with rel=me, with ap id", %{user: user, conn: conn} do
|
||||||
|
Tesla.Mock.mock(fn
|
||||||
|
%{url: "http://example.com/rel_me/ap_id"} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: ~s[<html><head><link rel="me" href="#{user.ap_id}"></head></html>]
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
field = %{name: "Website", value: "http://example.com/rel_me/ap_id"}
|
||||||
|
|
||||||
|
account_data =
|
||||||
|
conn
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{fields_attributes: [field]})
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert [
|
||||||
|
%{
|
||||||
|
"name" => "Website",
|
||||||
|
"value" =>
|
||||||
|
~s[<a href="http://example.com/rel_me/ap_id" rel="ugc">http://example.com/rel_me/ap_id</a>],
|
||||||
|
"verified_at" => verified_at
|
||||||
|
}
|
||||||
|
] = account_data["fields"]
|
||||||
|
|
||||||
|
{:ok, verified_at, _} = DateTime.from_iso8601(verified_at)
|
||||||
|
assert DateTime.diff(DateTime.utc_now(), verified_at) < 10
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update fields with a link to content with rel=me, with frontend path", %{
|
||||||
|
user: user,
|
||||||
|
conn: conn
|
||||||
|
} do
|
||||||
|
fe_url = "#{Pleroma.Web.Endpoint.url()}/#{user.nickname}"
|
||||||
|
|
||||||
|
Tesla.Mock.mock(fn
|
||||||
|
%{url: "http://example.com/rel_me/fe_path"} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: ~s[<html><head><link rel="me" href="#{fe_url}"></head></html>]
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
field = %{name: "Website", value: "http://example.com/rel_me/fe_path"}
|
||||||
|
|
||||||
|
account_data =
|
||||||
|
conn
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{fields_attributes: [field]})
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert [
|
||||||
|
%{
|
||||||
|
"name" => "Website",
|
||||||
|
"value" =>
|
||||||
|
~s[<a href="http://example.com/rel_me/fe_path" rel="ugc">http://example.com/rel_me/fe_path</a>],
|
||||||
|
"verified_at" => verified_at
|
||||||
|
}
|
||||||
|
] = account_data["fields"]
|
||||||
|
|
||||||
|
{:ok, verified_at, _} = DateTime.from_iso8601(verified_at)
|
||||||
|
assert DateTime.diff(DateTime.utc_now(), verified_at) < 10
|
||||||
|
end
|
||||||
|
|
||||||
test "emojis in fields labels", %{conn: conn} do
|
test "emojis in fields labels", %{conn: conn} do
|
||||||
fields = [
|
fields = [
|
||||||
%{name: ":firefox:", value: "is best 2hu"},
|
%{name: ":firefox:", value: "is best 2hu"},
|
||||||
|
|
|
@ -41,13 +41,13 @@ test "/user_exists", %{conn: conn} do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "/check_password", %{conn: conn} do
|
test "/check_password", %{conn: conn} do
|
||||||
user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("cool"))
|
user = insert(:user, password_hash: Pleroma.Password.hash_pwd_salt("cool"))
|
||||||
|
|
||||||
_deactivated_user =
|
_deactivated_user =
|
||||||
insert(:user,
|
insert(:user,
|
||||||
nickname: "konata",
|
nickname: "konata",
|
||||||
is_active: false,
|
is_active: false,
|
||||||
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("cool")
|
password_hash: Pleroma.Password.hash_pwd_salt("cool")
|
||||||
)
|
)
|
||||||
|
|
||||||
res =
|
res =
|
||||||
|
|
|
@ -18,7 +18,7 @@ defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do
|
||||||
@tag @skip
|
@tag @skip
|
||||||
test "authorizes the existing user using LDAP credentials" do
|
test "authorizes the existing user using LDAP credentials" do
|
||||||
password = "testpassword"
|
password = "testpassword"
|
||||||
user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
|
user = insert(:user, password_hash: Pleroma.Password.hash_pwd_salt(password))
|
||||||
app = insert(:oauth_app, scopes: ["read", "write"])
|
app = insert(:oauth_app, scopes: ["read", "write"])
|
||||||
|
|
||||||
host = Pleroma.Config.get([:ldap, :host]) |> to_charlist
|
host = Pleroma.Config.get([:ldap, :host]) |> to_charlist
|
||||||
|
@ -101,7 +101,7 @@ test "creates a new user after successful LDAP authorization" do
|
||||||
@tag @skip
|
@tag @skip
|
||||||
test "disallow authorization for wrong LDAP credentials" do
|
test "disallow authorization for wrong LDAP credentials" do
|
||||||
password = "testpassword"
|
password = "testpassword"
|
||||||
user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
|
user = insert(:user, password_hash: Pleroma.Password.hash_pwd_salt(password))
|
||||||
app = insert(:oauth_app, scopes: ["read", "write"])
|
app = insert(:oauth_app, scopes: ["read", "write"])
|
||||||
|
|
||||||
host = Pleroma.Config.get([:ldap, :host]) |> to_charlist
|
host = Pleroma.Config.get([:ldap, :host]) |> to_charlist
|
||||||
|
|
|
@ -20,7 +20,7 @@ defmodule Pleroma.Web.OAuth.MFAControllerTest do
|
||||||
insert(:user,
|
insert(:user,
|
||||||
multi_factor_authentication_settings: %MFA.Settings{
|
multi_factor_authentication_settings: %MFA.Settings{
|
||||||
enabled: true,
|
enabled: true,
|
||||||
backup_codes: [Pleroma.Password.Pbkdf2.hash_pwd_salt("test-code")],
|
backup_codes: [Pleroma.Password.hash_pwd_salt("test-code")],
|
||||||
totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
|
totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -246,7 +246,7 @@ test "returns access token with valid code", %{conn: conn, app: app} do
|
||||||
|
|
||||||
hashed_codes =
|
hashed_codes =
|
||||||
backup_codes
|
backup_codes
|
||||||
|> Enum.map(&Pleroma.Password.Pbkdf2.hash_pwd_salt(&1))
|
|> Enum.map(&Pleroma.Password.hash_pwd_salt(&1))
|
||||||
|
|
||||||
user =
|
user =
|
||||||
insert(:user,
|
insert(:user,
|
||||||
|
|
|
@ -316,7 +316,7 @@ test "with valid params, POST /oauth/register?op=connect redirects to `redirect_
|
||||||
app: app,
|
app: app,
|
||||||
conn: conn
|
conn: conn
|
||||||
} do
|
} do
|
||||||
user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("testpassword"))
|
user = insert(:user, password_hash: Pleroma.Password.hash_pwd_salt("testpassword"))
|
||||||
registration = insert(:registration, user: nil)
|
registration = insert(:registration, user: nil)
|
||||||
redirect_uri = OAuthController.default_redirect_uri(app)
|
redirect_uri = OAuthController.default_redirect_uri(app)
|
||||||
|
|
||||||
|
@ -347,7 +347,7 @@ test "with unlisted `redirect_uri`, POST /oauth/register?op=connect results in H
|
||||||
app: app,
|
app: app,
|
||||||
conn: conn
|
conn: conn
|
||||||
} do
|
} do
|
||||||
user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("testpassword"))
|
user = insert(:user, password_hash: Pleroma.Password.hash_pwd_salt("testpassword"))
|
||||||
registration = insert(:registration, user: nil)
|
registration = insert(:registration, user: nil)
|
||||||
unlisted_redirect_uri = "http://cross-site-request.com"
|
unlisted_redirect_uri = "http://cross-site-request.com"
|
||||||
|
|
||||||
|
@ -917,7 +917,7 @@ test "issues a token for an all-body request" do
|
||||||
|
|
||||||
test "issues a token for `password` grant_type with valid credentials, with full permissions by default" do
|
test "issues a token for `password` grant_type with valid credentials, with full permissions by default" do
|
||||||
password = "testpassword"
|
password = "testpassword"
|
||||||
user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
|
user = insert(:user, password_hash: Pleroma.Password.hash_pwd_salt(password))
|
||||||
|
|
||||||
app = insert(:oauth_app, scopes: ["read", "write"])
|
app = insert(:oauth_app, scopes: ["read", "write"])
|
||||||
|
|
||||||
|
@ -947,7 +947,7 @@ test "issues a mfa token for `password` grant_type, when MFA enabled" do
|
||||||
|
|
||||||
user =
|
user =
|
||||||
insert(:user,
|
insert(:user,
|
||||||
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password),
|
password_hash: Pleroma.Password.hash_pwd_salt(password),
|
||||||
multi_factor_authentication_settings: %MFA.Settings{
|
multi_factor_authentication_settings: %MFA.Settings{
|
||||||
enabled: true,
|
enabled: true,
|
||||||
totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
|
totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
|
||||||
|
@ -1056,7 +1056,7 @@ test "rejects token exchange for valid credentials belonging to unconfirmed user
|
||||||
password = "testpassword"
|
password = "testpassword"
|
||||||
|
|
||||||
{:ok, user} =
|
{:ok, user} =
|
||||||
insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
|
insert(:user, password_hash: Pleroma.Password.hash_pwd_salt(password))
|
||||||
|> User.confirmation_changeset(set_confirmation: false)
|
|> User.confirmation_changeset(set_confirmation: false)
|
||||||
|> User.update_and_set_cache()
|
|> User.update_and_set_cache()
|
||||||
|
|
||||||
|
@ -1084,7 +1084,7 @@ test "rejects token exchange for valid credentials belonging to deactivated user
|
||||||
|
|
||||||
user =
|
user =
|
||||||
insert(:user,
|
insert(:user,
|
||||||
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password),
|
password_hash: Pleroma.Password.hash_pwd_salt(password),
|
||||||
is_active: false
|
is_active: false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1112,7 +1112,7 @@ test "rejects token exchange for user with password_reset_pending set to true" d
|
||||||
|
|
||||||
user =
|
user =
|
||||||
insert(:user,
|
insert(:user,
|
||||||
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password),
|
password_hash: Pleroma.Password.hash_pwd_salt(password),
|
||||||
password_reset_pending: true
|
password_reset_pending: true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1141,7 +1141,7 @@ test "rejects token exchange for user with confirmation_pending set to true" do
|
||||||
|
|
||||||
user =
|
user =
|
||||||
insert(:user,
|
insert(:user,
|
||||||
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password),
|
password_hash: Pleroma.Password.hash_pwd_salt(password),
|
||||||
is_confirmed: false
|
is_confirmed: false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1169,7 +1169,7 @@ test "rejects token exchange for valid credentials belonging to an unapproved us
|
||||||
|
|
||||||
user =
|
user =
|
||||||
insert(:user,
|
insert(:user,
|
||||||
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password),
|
password_hash: Pleroma.Password.hash_pwd_salt(password),
|
||||||
is_approved: false
|
is_approved: false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -40,11 +40,11 @@ test "GET /api/v1/pleroma/emoji/packs", %{conn: conn} do
|
||||||
|> get("/api/v1/pleroma/emoji/packs")
|
|> get("/api/v1/pleroma/emoji/packs")
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
assert resp["count"] == 4
|
assert resp["count"] == 5
|
||||||
|
|
||||||
assert resp["packs"]
|
assert resp["packs"]
|
||||||
|> Map.keys()
|
|> Map.keys()
|
||||||
|> length() == 4
|
|> length() == 5
|
||||||
|
|
||||||
shared = resp["packs"]["test_pack"]
|
shared = resp["packs"]["test_pack"]
|
||||||
assert shared["files"] == %{"blank" => "blank.png", "blank2" => "blank2.png"}
|
assert shared["files"] == %{"blank" => "blank.png", "blank2" => "blank2.png"}
|
||||||
|
@ -61,7 +61,7 @@ test "GET /api/v1/pleroma/emoji/packs", %{conn: conn} do
|
||||||
|> get("/api/v1/pleroma/emoji/packs?page_size=1")
|
|> get("/api/v1/pleroma/emoji/packs?page_size=1")
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
assert resp["count"] == 4
|
assert resp["count"] == 5
|
||||||
|
|
||||||
packs = Map.keys(resp["packs"])
|
packs = Map.keys(resp["packs"])
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ test "GET /api/v1/pleroma/emoji/packs", %{conn: conn} do
|
||||||
|> get("/api/v1/pleroma/emoji/packs?page_size=1&page=2")
|
|> get("/api/v1/pleroma/emoji/packs?page_size=1&page=2")
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
assert resp["count"] == 4
|
assert resp["count"] == 5
|
||||||
packs = Map.keys(resp["packs"])
|
packs = Map.keys(resp["packs"])
|
||||||
assert length(packs) == 1
|
assert length(packs) == 1
|
||||||
[pack2] = packs
|
[pack2] = packs
|
||||||
|
@ -84,7 +84,7 @@ test "GET /api/v1/pleroma/emoji/packs", %{conn: conn} do
|
||||||
|> get("/api/v1/pleroma/emoji/packs?page_size=1&page=3")
|
|> get("/api/v1/pleroma/emoji/packs?page_size=1&page=3")
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
assert resp["count"] == 4
|
assert resp["count"] == 5
|
||||||
packs = Map.keys(resp["packs"])
|
packs = Map.keys(resp["packs"])
|
||||||
assert length(packs) == 1
|
assert length(packs) == 1
|
||||||
[pack3] = packs
|
[pack3] = packs
|
||||||
|
@ -94,7 +94,7 @@ test "GET /api/v1/pleroma/emoji/packs", %{conn: conn} do
|
||||||
|> get("/api/v1/pleroma/emoji/packs?page_size=1&page=4")
|
|> get("/api/v1/pleroma/emoji/packs?page_size=1&page=4")
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
assert resp["count"] == 4
|
assert resp["count"] == 5
|
||||||
packs = Map.keys(resp["packs"])
|
packs = Map.keys(resp["packs"])
|
||||||
assert length(packs) == 1
|
assert length(packs) == 1
|
||||||
[pack4] = packs
|
[pack4] = packs
|
||||||
|
@ -221,6 +221,24 @@ test "shared pack from remote and non shared from fallback-src", %{
|
||||||
url: "https://nonshared-pack"
|
url: "https://nonshared-pack"
|
||||||
} ->
|
} ->
|
||||||
text(File.read!("#{@emoji_path}/test_pack_nonshared/nonshared.zip"))
|
text(File.read!("#{@emoji_path}/test_pack_nonshared/nonshared.zip"))
|
||||||
|
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: "https://example.com/api/v1/pleroma/emoji/pack?name=test%20with%20spaces"
|
||||||
|
} ->
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/pleroma/emoji/pack?name=test%20with%20spaces")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|> json()
|
||||||
|
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: "https://example.com/api/v1/pleroma/emoji/packs/archive?name=test%20with%20spaces"
|
||||||
|
} ->
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/pleroma/emoji/packs/archive?name=test%20with%20spaces")
|
||||||
|
|> response(200)
|
||||||
|
|> text()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert admin_conn
|
assert admin_conn
|
||||||
|
@ -261,6 +279,18 @@ test "shared pack from remote and non shared from fallback-src", %{
|
||||||
|> json_response_and_validate_schema(200) == "ok"
|
|> json_response_and_validate_schema(200) == "ok"
|
||||||
|
|
||||||
refute File.exists?("#{@emoji_path}/test_pack_nonshared2")
|
refute File.exists?("#{@emoji_path}/test_pack_nonshared2")
|
||||||
|
|
||||||
|
assert admin_conn
|
||||||
|
|> put_req_header("content-type", "multipart/form-data")
|
||||||
|
|> post("/api/v1/pleroma/emoji/packs/download", %{
|
||||||
|
url: "https://example.com",
|
||||||
|
name: "test with spaces",
|
||||||
|
as: "test with spaces"
|
||||||
|
})
|
||||||
|
|> json_response_and_validate_schema(200) == "ok"
|
||||||
|
|
||||||
|
assert File.exists?("#{@emoji_path}/test with spaces/pack.json")
|
||||||
|
assert File.exists?("#{@emoji_path}/test with spaces/blank.png")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "nonshareable instance", %{admin_conn: admin_conn} do
|
test "nonshareable instance", %{admin_conn: admin_conn} do
|
||||||
|
|
|
@ -17,7 +17,7 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do
|
||||||
user = %User{
|
user = %User{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "dude",
|
name: "dude",
|
||||||
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("guy")
|
password_hash: Pleroma.Password.hash_pwd_salt("guy")
|
||||||
}
|
}
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
|
@ -52,7 +52,7 @@ test "with a correct password in the credentials, " <>
|
||||||
assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
|
assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "with a bcrypt hash, it updates to a pkbdf2 hash", %{conn: conn} do
|
test "with a bcrypt hash, it updates to an argon2 hash", %{conn: conn} do
|
||||||
user = insert(:user, password_hash: Bcrypt.hash_pwd_salt("123"))
|
user = insert(:user, password_hash: Bcrypt.hash_pwd_salt("123"))
|
||||||
assert "$2" <> _ = user.password_hash
|
assert "$2" <> _ = user.password_hash
|
||||||
|
|
||||||
|
@ -67,21 +67,17 @@ test "with a bcrypt hash, it updates to a pkbdf2 hash", %{conn: conn} do
|
||||||
assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
|
assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
|
||||||
|
|
||||||
user = User.get_by_id(user.id)
|
user = User.get_by_id(user.id)
|
||||||
assert "$pbkdf2" <> _ = user.password_hash
|
assert "$argon2" <> _ = user.password_hash
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag :skip_on_mac
|
test "with a pbkdf2 hash, it updates to an argon2 hash", %{conn: conn} do
|
||||||
test "with a crypt hash, it updates to a pkbdf2 hash", %{conn: conn} do
|
user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("123"))
|
||||||
user =
|
assert "$pbkdf2" <> _ = user.password_hash
|
||||||
insert(:user,
|
|
||||||
password_hash:
|
|
||||||
"$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
|
|
||||||
)
|
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:auth_user, user)
|
|> assign(:auth_user, user)
|
||||||
|> assign(:auth_credentials, %{password: "password"})
|
|> assign(:auth_credentials, %{password: "123"})
|
||||||
|> AuthenticationPlug.call(%{})
|
|> AuthenticationPlug.call(%{})
|
||||||
|
|
||||||
assert conn.assigns.user.id == conn.assigns.auth_user.id
|
assert conn.assigns.user.id == conn.assigns.auth_user.id
|
||||||
|
@ -89,7 +85,7 @@ test "with a crypt hash, it updates to a pkbdf2 hash", %{conn: conn} do
|
||||||
assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
|
assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
|
||||||
|
|
||||||
user = User.get_by_id(user.id)
|
user = User.get_by_id(user.id)
|
||||||
assert "$pbkdf2" <> _ = user.password_hash
|
assert "$argon2" <> _ = user.password_hash
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "checkpw/2" do
|
describe "checkpw/2" do
|
||||||
|
@ -101,14 +97,6 @@ test "check pbkdf2 hash" do
|
||||||
refute AuthenticationPlug.checkpw("test-password1", hash)
|
refute AuthenticationPlug.checkpw("test-password1", hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag :skip_on_mac
|
|
||||||
test "check sha512-crypt hash" do
|
|
||||||
hash =
|
|
||||||
"$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
|
|
||||||
|
|
||||||
assert AuthenticationPlug.checkpw("password", hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "check bcrypt hash" do
|
test "check bcrypt hash" do
|
||||||
hash = "$2a$10$uyhC/R/zoE1ndwwCtMusK.TLVzkQ/Ugsbqp3uXI.CTTz0gBw.24jS"
|
hash = "$2a$10$uyhC/R/zoE1ndwwCtMusK.TLVzkQ/Ugsbqp3uXI.CTTz0gBw.24jS"
|
||||||
|
|
||||||
|
@ -116,6 +104,14 @@ test "check bcrypt hash" do
|
||||||
refute AuthenticationPlug.checkpw("password1", hash)
|
refute AuthenticationPlug.checkpw("password1", hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "check argon2 hash" do
|
||||||
|
hash =
|
||||||
|
"$argon2id$v=19$m=65536,t=8,p=2$zEMMsTuK5KkL5AFWbX7jyQ$VyaQD7PF6e9btz0oH1YiAkWwIGZ7WNDZP8l+a/O171g"
|
||||||
|
|
||||||
|
assert AuthenticationPlug.checkpw("password", hash)
|
||||||
|
refute AuthenticationPlug.checkpw("password1", hash)
|
||||||
|
end
|
||||||
|
|
||||||
test "it returns false when hash invalid" do
|
test "it returns false when hash invalid" do
|
||||||
hash =
|
hash =
|
||||||
"psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
|
"psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
|
||||||
|
|
68
test/pleroma/web/plugs/ensure_http_signature_plug_test.exs
Normal file
68
test/pleroma/web/plugs/ensure_http_signature_plug_test.exs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
# Akkoma: Magically expressive social media
|
||||||
|
# Copyright © 2022-2022 Akkoma Authors <https://akkoma.dev/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.Plugs.EnsureHTTPSignaturePlugTest do
|
||||||
|
use Pleroma.Web.ConnCase
|
||||||
|
alias Pleroma.Web.Plugs.EnsureHTTPSignaturePlug
|
||||||
|
|
||||||
|
import Plug.Conn
|
||||||
|
import Phoenix.Controller, only: [put_format: 2]
|
||||||
|
|
||||||
|
import Pleroma.Tests.Helpers, only: [clear_config: 2]
|
||||||
|
|
||||||
|
describe "requires a signature when `authorized_fetch_mode` is enabled" do
|
||||||
|
setup do
|
||||||
|
clear_config([:activitypub, :authorized_fetch_mode], true)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn(:get, "/doesntmatter")
|
||||||
|
|> put_format("activity+json")
|
||||||
|
|
||||||
|
[conn: conn]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "and signature has been set as invalid", %{conn: conn} do
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:valid_signature, false)
|
||||||
|
|> EnsureHTTPSignaturePlug.call(%{})
|
||||||
|
|
||||||
|
assert conn.halted == true
|
||||||
|
assert conn.status == 401
|
||||||
|
assert conn.state == :sent
|
||||||
|
assert conn.resp_body == "Request not signed"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "and signature has been set as valid", %{conn: conn} do
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:valid_signature, true)
|
||||||
|
|> EnsureHTTPSignaturePlug.call(%{})
|
||||||
|
|
||||||
|
assert conn.halted == false
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does nothing for non-ActivityPub content types", %{conn: conn} do
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:valid_signature, false)
|
||||||
|
|> put_format("html")
|
||||||
|
|> EnsureHTTPSignaturePlug.call(%{})
|
||||||
|
|
||||||
|
assert conn.halted == false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does nothing on invalid signature when `authorized_fetch_mode` is disabled" do
|
||||||
|
clear_config([:activitypub, :authorized_fetch_mode], false)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn(:get, "/doesntmatter")
|
||||||
|
|> put_format("activity+json")
|
||||||
|
|> assign(:valid_signature, false)
|
||||||
|
|> EnsureHTTPSignaturePlug.call(%{})
|
||||||
|
|
||||||
|
assert conn.halted == false
|
||||||
|
end
|
||||||
|
end
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue