Merge branch 'main' into better-caching
This commit is contained in:
commit
d44c034f6a
@ -10,7 +10,7 @@ executors:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
docker:
|
docker:
|
||||||
- image: plumeorg/plume-buildenv:v0.0.9
|
- image: plumeorg/plume-buildenv:v0.2.0
|
||||||
- image: <<#parameters.postgres>>circleci/postgres:9.6-alpine<</parameters.postgres>><<^parameters.postgres>>alpine:latest<</parameters.postgres>>
|
- image: <<#parameters.postgres>>circleci/postgres:9.6-alpine<</parameters.postgres>><<^parameters.postgres>>alpine:latest<</parameters.postgres>>
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM debian:stretch-20190326
|
FROM debian:buster-20201117
|
||||||
ENV PATH="/root/.cargo/bin:${PATH}"
|
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||||
|
|
||||||
#install native/circleci/build dependancies
|
#install native/circleci/build dependancies
|
||||||
|
@ -45,3 +45,12 @@ ROCKET_ADDRESS=127.0.0.1
|
|||||||
#PLUME_LOGO_192=icons/trwnh/paragraphs/plumeParagraphs192.png
|
#PLUME_LOGO_192=icons/trwnh/paragraphs/plumeParagraphs192.png
|
||||||
#PLUME_LOGO_256=icons/trwnh/paragraphs/plumeParagraphs256.png
|
#PLUME_LOGO_256=icons/trwnh/paragraphs/plumeParagraphs256.png
|
||||||
#PLUME_LOGO_512=icons/trwnh/paragraphs/plumeParagraphs512.png
|
#PLUME_LOGO_512=icons/trwnh/paragraphs/plumeParagraphs512.png
|
||||||
|
|
||||||
|
## LDAP CONFIG ##
|
||||||
|
# the object that will be bound is "${USER_NAME_ATTR}=${username},${BASE_DN}"
|
||||||
|
#LDAP_ADDR=ldap://127.0.0.1:1389
|
||||||
|
#LDAP_BASE_DN="ou=users,dc=your-org,dc=eu"
|
||||||
|
#LDAP_USER_NAME_ATTR=cn
|
||||||
|
#LDAP_USER_MAIL_ATTR=mail
|
||||||
|
#LDAP_TLS=false
|
||||||
|
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -18,3 +18,4 @@ tags.*
|
|||||||
search_index
|
search_index
|
||||||
.buildconfig
|
.buildconfig
|
||||||
__pycache__
|
__pycache__
|
||||||
|
.vscode/
|
||||||
|
158
CHANGELOG.md
Normal file
158
CHANGELOG.md
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
<!-- next-header -->
|
||||||
|
|
||||||
|
## [Unreleased] - No release date
|
||||||
|
|
||||||
|
## [0.5.0] - 2020-06-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Email blocklisting (#718)
|
||||||
|
- Syntax highlighting (#691)
|
||||||
|
- Persian localization (#782)
|
||||||
|
- Switchable tokenizer - enables Japanese full-text search (#776)
|
||||||
|
- Make database connections configurable by environment variables (#768)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Display likes and boost on post cards (#744)
|
||||||
|
- Rust 2018 (#726)
|
||||||
|
- Bump to LLVM to 9.0.0 to fix ARM builds (#737)
|
||||||
|
- Remove dependency on runtime-fmt (#773)
|
||||||
|
- Drop the -alpha suffix in release names, it is implied that Plume is not stable yet because of the 0 major version (Plume 1.0.0 will be the first stable release).
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix parsing of mentions inside a Markdown code block (be430c6)
|
||||||
|
- Fix RSS issues (#720)
|
||||||
|
- Fix Atom feed (#764)
|
||||||
|
- Fix default theme (#746)
|
||||||
|
- Fix shown password on remote interact pages (#741)
|
||||||
|
- Allow unicode hashtags (#757)
|
||||||
|
- Fix French grammar for for 0 (#760)
|
||||||
|
- Don't show boosts and likes for "all" and "local" in timelines (#781)
|
||||||
|
- Fix liking and boosting posts on remote instances (#762)
|
||||||
|
|
||||||
|
## [0.4.0] - 2019-12-23
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add support for generic timeline (#525)
|
||||||
|
- Federate user deletion (#551)
|
||||||
|
- import migrations and don't require diesel_cli for admins (#555)
|
||||||
|
- Cache local instance (#572)
|
||||||
|
- Initial RTL support #575 (#577)
|
||||||
|
- Confirm deletion of blog (#602)
|
||||||
|
- Make a distinction between moderators and admins (#619)
|
||||||
|
- Theming (#624)
|
||||||
|
- Add clap to plume in order to print help and version (#631)
|
||||||
|
- Add Snapcraft metadata and install/maintenance hooks (#666)
|
||||||
|
- Add environmental variable to control path of media (#683)
|
||||||
|
- Add autosaving to the editor (#688)
|
||||||
|
- CI: Upload artifacts to pull request deploy environment (#539)
|
||||||
|
- CI: Upload artifact of wasm binary (#571)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Update follow_remote.rs.html grammar (#548)
|
||||||
|
- Add some feedback when performing some actions (#552)
|
||||||
|
- Theme update (#553)
|
||||||
|
- Remove the new index lock tantivy uses (#556)
|
||||||
|
- Reduce reqwest timeout to 5s (#557)
|
||||||
|
- Improve notification management (#561)
|
||||||
|
- Fix occurrences of 'have been' to 'has been' (#578) + Direct follow-up to #578 (#603)
|
||||||
|
- Store password reset requests in database (#610)
|
||||||
|
- Use futures and tokio to send activities (#620)
|
||||||
|
- Don't ignore dotenv errors (#630)
|
||||||
|
- Replace the input! macro with an Input builder (#646)
|
||||||
|
- Update default license (#659)
|
||||||
|
- Paginate the outbox responses. Fixes #669 (#681)
|
||||||
|
- Use the "classic" editor by default (#697)
|
||||||
|
- Fix issue #705 (#708)
|
||||||
|
- Make comments in styleshhets a bit clearer (#545)
|
||||||
|
- Rewrite circleci config (#558)
|
||||||
|
- Use openssl instead of sha256sum for build.rs (#568)
|
||||||
|
- Update dependencies (#574)
|
||||||
|
- Refactor code to use Shrinkwraprs and diesel-derive-newtype (#598)
|
||||||
|
- Add enum containing all successful route returns (#614)
|
||||||
|
- Update dependencies which depended on nix -- fixes arm32 builds (#615)
|
||||||
|
- Update some documents (#616)
|
||||||
|
- Update dependencies (#643)
|
||||||
|
- Make the comment syntax consistent across all CSS (#487)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Remove r (#535)
|
||||||
|
- Fix certain improper rendering of forms (#560)
|
||||||
|
- make hashtags work in profile summary (#562)
|
||||||
|
- Fix some federation issue (#573)
|
||||||
|
- Prevent comment form submit button distortion on iOS (#592)
|
||||||
|
- Update textarea overflow to scroll (#609)
|
||||||
|
- Fix arm builds (#612)
|
||||||
|
- Fix theme caching (#647)
|
||||||
|
- Fix issue #642, frontend not in English if the user language does not exist (#648)
|
||||||
|
- Don't index drafts (#656)
|
||||||
|
- Fill entirely user on creation (#657)
|
||||||
|
- Delete notification on user deletion (#658)
|
||||||
|
- Order media so that latest added are top (#660)
|
||||||
|
- Fix logo URL (#664)
|
||||||
|
- Snap: Ensure cargo-web doesn't erroneously adopt our workspace. (#667)
|
||||||
|
- Snap: Another fix for building (#668)
|
||||||
|
- Snap: Fix build for non-Tier-1 Rust platforms (#672)
|
||||||
|
- Don't split sentences for translations (#677)
|
||||||
|
- Escape href quotation marks (#678)
|
||||||
|
- Re-add empty strings in translation (#682)
|
||||||
|
- Make the search index creation during migration respect SEARCH_INDEX (#689)
|
||||||
|
- Fix the navigation menu not opening on touch (#690)
|
||||||
|
- Make search items optional (#693)
|
||||||
|
- Various snap fixes (#698)
|
||||||
|
- Fix #637 : Markdown footnotes (#700)
|
||||||
|
- Fix lettre (#706)
|
||||||
|
- CI: Fix Crowdin upload (#576)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Remove the Canapi dependency (#540)
|
||||||
|
- Remove use of Rust in migrations (#704)
|
||||||
|
|
||||||
|
## [0.3.0] - 2019-04-19
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Cover for articles (#299, #387)
|
||||||
|
- Password reset (#448)
|
||||||
|
- New editor (#293, #458, #482, #483, #486, #530)
|
||||||
|
- Search (#324, #375, #445)
|
||||||
|
- Edit blogs (#460, #494, #497)
|
||||||
|
- Hashtags in articles (#283, #295)
|
||||||
|
- API endpoints (#245, #285, #307)
|
||||||
|
- A bunch of new translations! (#479, #501, #506, #510, #512, #514)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Federation improvements (#216, #217, #357, #364, #399, #443, #446, #455, #502, #519)
|
||||||
|
- Improved build process (#281, #374, #392, #402, #489, #498, #503, #511, #513, #515, #528)
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- UI usability fixes (#370, #386, #401, #417, #418, #444, #452, #480, #516, #518, #522, #532)
|
||||||
|
|
||||||
|
## [0.2.0] - 2018-09-12
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Article publishing, or save as a draft
|
||||||
|
- Like, or boost an article
|
||||||
|
- Basic Markdown editor
|
||||||
|
- Federated commenting system
|
||||||
|
- User account creation
|
||||||
|
- Limited federation on other platforms and subscribing to users
|
||||||
|
- Ability to create multiple blogs
|
||||||
|
|
||||||
|
<!-- next-url -->
|
||||||
|
[Unreleased]: https://github.com/Plume-org/Plume/compare/0.5.0...HEAD
|
||||||
|
[0.5.0]: https://github.com/Plume-org/Plume/compare/0.4.0-alpha-4...0.5.0
|
||||||
|
[0.4.0]: https://github.com/Plume-org/Plume/compare/0.3.0-alpha-2...0.4.0-alpha-4
|
||||||
|
[0.3.0]: https://github.com/Plume-org/Plume/compare/0.2.0-alpha-1...0.3.0-alpha-2
|
||||||
|
[0.2.0]: https://github.com/Plume-org/Plume/releases/tag/0.2.0-alpha-1
|
1952
Cargo.lock
generated
1952
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["Plume contributors"]
|
authors = ["Plume contributors"]
|
||||||
name = "plume"
|
name = "plume"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
repository = "https://github.com/Plume-org/Plume"
|
repository = "https://github.com/Plume-org/Plume"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
@ -20,8 +20,8 @@ heck = "0.3.0"
|
|||||||
lettre = "0.9.2"
|
lettre = "0.9.2"
|
||||||
lettre_email = "0.9.2"
|
lettre_email = "0.9.2"
|
||||||
num_cpus = "1.10"
|
num_cpus = "1.10"
|
||||||
rocket = "0.4.2"
|
rocket = "0.4.5"
|
||||||
rocket_contrib = { version = "0.4.2", features = ["json"] }
|
rocket_contrib = { version = "0.4.5", features = ["json"] }
|
||||||
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
|
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
|
||||||
rpassword = "4.0"
|
rpassword = "4.0"
|
||||||
scheduled-thread-pool = "0.2.2"
|
scheduled-thread-pool = "0.2.2"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM rust:1-stretch as builder
|
FROM rust:1-buster as builder
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
@ -28,7 +28,7 @@ RUN cargo install --path ./ --force --no-default-features --features postgres
|
|||||||
RUN cargo install --path plume-cli --force --no-default-features --features postgres
|
RUN cargo install --path plume-cli --force --no-default-features --features postgres
|
||||||
RUN cargo clean
|
RUN cargo clean
|
||||||
|
|
||||||
FROM debian:stretch-slim
|
FROM debian:buster-slim
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM rust:1-stretch
|
FROM rust:1-buster
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
|
@ -40,7 +40,7 @@ main header.article {
|
|||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
h1, .article-info {
|
h1, .article-info {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -490,3 +490,30 @@ input:checked ~ .cw-container > .cw-text {
|
|||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Small screens
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
#plume-editor header {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
|
||||||
|
button {
|
||||||
|
flex: 0 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup {
|
||||||
|
top: 10vh;
|
||||||
|
bottom: 10vh;
|
||||||
|
left: 1vw;
|
||||||
|
right: 1vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
main article {
|
||||||
|
margin: 2.5em .5em;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
main .article-meta > *, main .article-meta .comments, main .article-meta > .banner > * {
|
||||||
|
margin: 0 5%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -490,6 +490,10 @@ figure {
|
|||||||
|
|
||||||
/// Small screens
|
/// Small screens
|
||||||
@media screen and (max-width: 600px) {
|
@media screen and (max-width: 600px) {
|
||||||
|
body > main > *, .h-feed > * {
|
||||||
|
margin: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
main .article-meta {
|
main .article-meta {
|
||||||
> *, .comments {
|
> *, .comments {
|
||||||
margin: 0 5%;
|
margin: 0 5%;
|
||||||
@ -535,7 +539,7 @@ figure {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
max-width: 100%;
|
max-width: 100% !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,6 +205,7 @@ body > header {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, 0);
|
transform: translate(-50%, 0);
|
||||||
|
transform: translateZ(0);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@ -221,3 +222,93 @@ body > header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Small screens
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
@keyframes menuOpening {
|
||||||
|
from {
|
||||||
|
transform: scaleX(0);
|
||||||
|
transform-origin: left;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: scaleX(1);
|
||||||
|
transform-origin: left;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body > header {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
nav#menu {
|
||||||
|
display: inline-flex;
|
||||||
|
z-index: 21;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
display: none;
|
||||||
|
appearance: none;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body > header:focus-within #content, #content.show {
|
||||||
|
position: fixed;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
animation: 0.2s menuOpening;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
transform: skewX(-10deg);
|
||||||
|
top: 0;
|
||||||
|
left: -20%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
z-index: -10;
|
||||||
|
|
||||||
|
background: $primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
> nav {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
color: $background;
|
||||||
|
font-size: 1.4em;
|
||||||
|
font-weight: 300;
|
||||||
|
|
||||||
|
&.title { font-size: 1.8em; }
|
||||||
|
|
||||||
|
> *:first-child { width: 3rem; }
|
||||||
|
> img:first-child { height: 3rem; }
|
||||||
|
> *:last-child { margin-left: 1rem; }
|
||||||
|
> nav hr {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
border: solid $background 0.1rem;
|
||||||
|
}
|
||||||
|
.mobile-label { display: initial; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
"project_identifier": "plume"
|
"project_id": 352097
|
||||||
"api_key_env": CROWDIN_API_KEY
|
"api_token_env": "CROWDIN_API_KEY"
|
||||||
preserve_hierarchy: true
|
preserve_hierarchy: true
|
||||||
files:
|
files:
|
||||||
- source: /po/plume/plume.pot
|
- source: /po/plume/plume.pot
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "plume-api"
|
name = "plume-api"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
authors = ["Plume contributors"]
|
authors = ["Plume contributors"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
1
plume-api/release.toml
Normal file
1
plume-api/release.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
pre-release-replacements = []
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "plume-cli"
|
name = "plume-cli"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
authors = ["Plume contributors"]
|
authors = ["Plume contributors"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
1
plume-cli/release.toml
Normal file
1
plume-cli/release.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
pre-release-replacements = []
|
@ -132,7 +132,7 @@ fn new<'a>(args: &ArgMatches<'a>, conn: &Connection) {
|
|||||||
role,
|
role,
|
||||||
&bio,
|
&bio,
|
||||||
email,
|
email,
|
||||||
User::hash_pass(&password).expect("Couldn't hash password"),
|
Some(User::hash_pass(&password).expect("Couldn't hash password")),
|
||||||
)
|
)
|
||||||
.expect("Couldn't save new user");
|
.expect("Couldn't save new user");
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "plume-common"
|
name = "plume-common"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
authors = ["Plume contributors"]
|
authors = ["Plume contributors"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ heck = "0.3.0"
|
|||||||
hex = "0.3"
|
hex = "0.3"
|
||||||
hyper = "0.12.33"
|
hyper = "0.12.33"
|
||||||
openssl = "0.10.22"
|
openssl = "0.10.22"
|
||||||
rocket = "0.4.0"
|
rocket = "0.4.5"
|
||||||
reqwest = "0.9"
|
reqwest = "0.9"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
|
1
plume-common/release.toml
Normal file
1
plume-common/release.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
pre-release-replacements = []
|
@ -294,8 +294,7 @@ pub fn md_to_html<'a>(
|
|||||||
}
|
}
|
||||||
let hashtag = text_acc;
|
let hashtag = text_acc;
|
||||||
let link = Tag::Link(
|
let link = Tag::Link(
|
||||||
format!("{}tag/{}", base_url, &hashtag.to_camel_case())
|
format!("{}tag/{}", base_url, &hashtag).into(),
|
||||||
.into(),
|
|
||||||
hashtag.to_owned().into(),
|
hashtag.to_owned().into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "plume-front"
|
name = "plume-front"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
authors = ["Plume contributors"]
|
authors = ["Plume contributors"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
1
plume-front/release.toml
Normal file
1
plume-front/release.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
pre-release-replacements = []
|
@ -269,7 +269,13 @@ pub fn init() -> Result<(), EditorError> {
|
|||||||
let editor_button = document().create_element("a")?;
|
let editor_button = document().create_element("a")?;
|
||||||
js! { @{&editor_button}.href = "#"; }
|
js! { @{&editor_button}.href = "#"; }
|
||||||
editor_button.add_event_listener(|_: ClickEvent| {
|
editor_button.add_event_listener(|_: ClickEvent| {
|
||||||
window().local_storage().remove("basic-editor");
|
if window()
|
||||||
|
.local_storage()
|
||||||
|
.insert("basic-editor", "false")
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
console!(log, "Failed to write into local storage");
|
||||||
|
}
|
||||||
window().history().go(0).ok(); // refresh
|
window().history().go(0).ok(); // refresh
|
||||||
});
|
});
|
||||||
editor_button.append_child(
|
editor_button.append_child(
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "plume-macro"
|
name = "plume-macro"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
authors = ["Trinity Pointard <trinity.pointard@insa-rennes.fr>"]
|
authors = ["Trinity Pointard <trinity.pointard@insa-rennes.fr>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Plume procedural macros"
|
description = "Plume procedural macros"
|
||||||
|
1
plume-macro/release.toml
Normal file
1
plume-macro/release.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
pre-release-replacements = []
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "plume-models"
|
name = "plume-models"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
authors = ["Plume contributors"]
|
authors = ["Plume contributors"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
@ -13,9 +13,10 @@ guid-create = "0.1"
|
|||||||
heck = "0.3.0"
|
heck = "0.3.0"
|
||||||
itertools = "0.8.0"
|
itertools = "0.8.0"
|
||||||
lazy_static = "1.0"
|
lazy_static = "1.0"
|
||||||
|
ldap3 = "0.7.1"
|
||||||
migrations_internals= "1.4.0"
|
migrations_internals= "1.4.0"
|
||||||
openssl = "0.10.22"
|
openssl = "0.10.22"
|
||||||
rocket = "0.4.0"
|
rocket = "0.4.5"
|
||||||
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
|
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
|
||||||
reqwest = "0.9"
|
reqwest = "0.9"
|
||||||
scheduled-thread-pool = "0.2.2"
|
scheduled-thread-pool = "0.2.2"
|
||||||
@ -30,7 +31,7 @@ whatlang = "0.7.1"
|
|||||||
shrinkwraprs = "0.2.1"
|
shrinkwraprs = "0.2.1"
|
||||||
diesel-derive-newtype = "0.1.2"
|
diesel-derive-newtype = "0.1.2"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
lindera-tantivy = { version = "0.1.2", optional = true }
|
lindera-tantivy = { version = "0.1.3", optional = true }
|
||||||
|
|
||||||
[dependencies.chrono]
|
[dependencies.chrono]
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
|
BIN
plume-models/plume.db-journal
Normal file
BIN
plume-models/plume.db-journal
Normal file
Binary file not shown.
BIN
plume-models/plume_tests.sqlite-journal
Normal file
BIN
plume-models/plume_tests.sqlite-journal
Normal file
Binary file not shown.
1
plume-models/release.toml
Normal file
1
plume-models/release.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
pre-release-replacements = []
|
@ -20,6 +20,7 @@ pub struct Config {
|
|||||||
pub logo: LogoConfig,
|
pub logo: LogoConfig,
|
||||||
pub default_theme: String,
|
pub default_theme: String,
|
||||||
pub media_directory: String,
|
pub media_directory: String,
|
||||||
|
pub ldap: Option<LdapConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -240,6 +241,42 @@ impl SearchTokenizerConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct LdapConfig {
|
||||||
|
pub addr: String,
|
||||||
|
pub base_dn: String,
|
||||||
|
pub tls: bool,
|
||||||
|
pub user_name_attr: String,
|
||||||
|
pub mail_attr: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_ldap_config() -> Option<LdapConfig> {
|
||||||
|
let addr = var("LDAP_ADDR").ok();
|
||||||
|
let base_dn = var("LDAP_BASE_DN").ok();
|
||||||
|
match (addr, base_dn) {
|
||||||
|
(Some(addr), Some(base_dn)) => {
|
||||||
|
let tls = var("LDAP_TLS").unwrap_or_else(|_| "false".to_owned());
|
||||||
|
let tls = match tls.as_ref() {
|
||||||
|
"1" | "true" | "TRUE" => true,
|
||||||
|
"0" | "false" | "FALSE" => false,
|
||||||
|
_ => panic!("Invalid LDAP configuration : tls"),
|
||||||
|
};
|
||||||
|
let user_name_attr = var("LDAP_USER_NAME_ATTR").unwrap_or_else(|_| "cn".to_owned());
|
||||||
|
let mail_attr = var("LDAP_USER_MAIL_ATTR").unwrap_or_else(|_| "mail".to_owned());
|
||||||
|
Some(LdapConfig {
|
||||||
|
addr,
|
||||||
|
base_dn,
|
||||||
|
tls,
|
||||||
|
user_name_attr,
|
||||||
|
mail_attr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
(None, None) => None,
|
||||||
|
(_, _) => {
|
||||||
|
panic!("Invalid LDAP configuration : both LDAP_ADDR and LDAP_BASE_DN must be set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref CONFIG: Config = Config {
|
pub static ref CONFIG: Config = Config {
|
||||||
base_url: var("BASE_URL").unwrap_or_else(|_| format!(
|
base_url: var("BASE_URL").unwrap_or_else(|_| format!(
|
||||||
@ -267,5 +304,6 @@ lazy_static! {
|
|||||||
default_theme: var("DEFAULT_THEME").unwrap_or_else(|_| "default-light".to_owned()),
|
default_theme: var("DEFAULT_THEME").unwrap_or_else(|_| "default-light".to_owned()),
|
||||||
media_directory: var("MEDIA_UPLOAD_DIRECTORY")
|
media_directory: var("MEDIA_UPLOAD_DIRECTORY")
|
||||||
.unwrap_or_else(|_| "static/media".to_owned()),
|
.unwrap_or_else(|_| "static/media".to_owned()),
|
||||||
|
ldap: get_ldap_config(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ use activitypub::{
|
|||||||
};
|
};
|
||||||
use chrono::{NaiveDateTime, TimeZone, Utc};
|
use chrono::{NaiveDateTime, TimeZone, Utc};
|
||||||
use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
|
use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
|
||||||
use heck::{CamelCase, KebabCase};
|
use heck::KebabCase;
|
||||||
use plume_common::{
|
use plume_common::{
|
||||||
activity_pub::{
|
activity_pub::{
|
||||||
inbox::{AsObject, FromId},
|
inbox::{AsObject, FromId},
|
||||||
@ -622,7 +622,6 @@ impl FromId<PlumeRocket> for Post {
|
|||||||
let mut hashtags = md_to_html(&post.source, None, false, None)
|
let mut hashtags = md_to_html(&post.source, None, false, None)
|
||||||
.2
|
.2
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| s.to_camel_case())
|
|
||||||
.collect::<HashSet<_>>();
|
.collect::<HashSet<_>>();
|
||||||
if let Some(serde_json::Value::Array(tags)) = article.object_props.tag {
|
if let Some(serde_json::Value::Array(tags)) = article.object_props.tag {
|
||||||
for tag in tags {
|
for tag in tags {
|
||||||
@ -762,7 +761,6 @@ impl AsObject<User, Update, &PlumeRocket> for PostUpdate {
|
|||||||
let mut txt_hashtags = md_to_html(&post.source, None, false, None)
|
let mut txt_hashtags = md_to_html(&post.source, None, false, None)
|
||||||
.2
|
.2
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| s.to_camel_case())
|
|
||||||
.collect::<HashSet<_>>();
|
.collect::<HashSet<_>>();
|
||||||
if let Some(serde_json::Value::Array(mention_tags)) = self.tags {
|
if let Some(serde_json::Value::Array(mention_tags)) = self.tags {
|
||||||
let mut mentions = vec![];
|
let mut mentions = vec![];
|
||||||
|
@ -5,10 +5,10 @@ use crate::{
|
|||||||
use chrono::Datelike;
|
use chrono::Datelike;
|
||||||
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::{cmp, fs::create_dir_all, path::Path, sync::Mutex};
|
use std::{cmp, fs::create_dir_all, io, path::Path, sync::Mutex};
|
||||||
use tantivy::{
|
use tantivy::{
|
||||||
collector::TopDocs, directory::MmapDirectory, schema::*, Index, IndexReader, IndexWriter,
|
collector::TopDocs, directory::MmapDirectory, schema::*, Index, IndexReader, IndexWriter,
|
||||||
ReloadPolicy, Term,
|
ReloadPolicy, TantivyError, Term,
|
||||||
};
|
};
|
||||||
use whatlang::{detect as detect_lang, Lang};
|
use whatlang::{detect as detect_lang, Lang};
|
||||||
|
|
||||||
@ -18,6 +18,7 @@ pub enum SearcherError {
|
|||||||
WriteLockAcquisitionError,
|
WriteLockAcquisitionError,
|
||||||
IndexOpeningError,
|
IndexOpeningError,
|
||||||
IndexEditionError,
|
IndexEditionError,
|
||||||
|
InvalidIndexDataError,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Searcher {
|
pub struct Searcher {
|
||||||
@ -135,7 +136,19 @@ impl Searcher {
|
|||||||
.reader_builder()
|
.reader_builder()
|
||||||
.reload_policy(ReloadPolicy::Manual)
|
.reload_policy(ReloadPolicy::Manual)
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| SearcherError::IndexCreationError)?,
|
.map_err(|e| {
|
||||||
|
if let TantivyError::IOError(err) = e {
|
||||||
|
let err: io::Error = err.into();
|
||||||
|
if err.kind() == io::ErrorKind::InvalidData {
|
||||||
|
// Search index was created in older Tantivy format.
|
||||||
|
SearcherError::InvalidIndexDataError
|
||||||
|
} else {
|
||||||
|
SearcherError::IndexCreationError
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SearcherError::IndexCreationError
|
||||||
|
}
|
||||||
|
})?,
|
||||||
index,
|
index,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ap_url, blocklisted_emails::BlocklistedEmail, blogs::Blog, db_conn::DbConn, follows::Follow,
|
ap_url, blocklisted_emails::BlocklistedEmail, blogs::Blog, config::CONFIG, db_conn::DbConn,
|
||||||
instance::*, medias::Media, notifications::Notification, post_authors::PostAuthor, posts::Post,
|
follows::Follow, instance::*, medias::Media, notifications::Notification,
|
||||||
safe_string::SafeString, schema::users, search::Searcher, timeline::Timeline, Connection,
|
post_authors::PostAuthor, posts::Post, safe_string::SafeString, schema::users,
|
||||||
Error, PlumeRocket, Result, ITEMS_PER_PAGE,
|
search::Searcher, timeline::Timeline, Connection, Error, PlumeRocket, Result, ITEMS_PER_PAGE,
|
||||||
};
|
};
|
||||||
use activitypub::{
|
use activitypub::{
|
||||||
activity::Delete,
|
activity::Delete,
|
||||||
@ -14,6 +14,7 @@ use activitypub::{
|
|||||||
use bcrypt;
|
use bcrypt;
|
||||||
use chrono::{NaiveDateTime, Utc};
|
use chrono::{NaiveDateTime, Utc};
|
||||||
use diesel::{self, BelongingToDsl, ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl};
|
use diesel::{self, BelongingToDsl, ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl};
|
||||||
|
use ldap3::{LdapConn, Scope, SearchEntry};
|
||||||
use openssl::{
|
use openssl::{
|
||||||
hash::MessageDigest,
|
hash::MessageDigest,
|
||||||
pkey::{PKey, Private},
|
pkey::{PKey, Private},
|
||||||
@ -292,11 +293,116 @@ impl User {
|
|||||||
bcrypt::hash(pass, 10).map_err(Error::from)
|
bcrypt::hash(pass, 10).map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn auth(&self, pass: &str) -> bool {
|
fn ldap_register(conn: &Connection, name: &str, password: &str) -> Result<User> {
|
||||||
self.hashed_password
|
if CONFIG.ldap.is_none() {
|
||||||
.clone()
|
return Err(Error::NotFound);
|
||||||
.map(|hashed| bcrypt::verify(pass, hashed.as_ref()).unwrap_or(false))
|
}
|
||||||
.unwrap_or(false)
|
let ldap = CONFIG.ldap.as_ref().unwrap();
|
||||||
|
|
||||||
|
let mut ldap_conn = LdapConn::new(&ldap.addr).map_err(|_| Error::NotFound)?;
|
||||||
|
let ldap_name = format!("{}={},{}", ldap.user_name_attr, name, ldap.base_dn);
|
||||||
|
let bind = ldap_conn
|
||||||
|
.simple_bind(&ldap_name, password)
|
||||||
|
.map_err(|_| Error::NotFound)?;
|
||||||
|
|
||||||
|
if bind.success().is_err() {
|
||||||
|
return Err(Error::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
let search = ldap_conn
|
||||||
|
.search(
|
||||||
|
&ldap_name,
|
||||||
|
Scope::Base,
|
||||||
|
"(|(objectClass=person)(objectClass=user))",
|
||||||
|
vec![&ldap.mail_attr],
|
||||||
|
)
|
||||||
|
.map_err(|_| Error::NotFound)?
|
||||||
|
.success()
|
||||||
|
.map_err(|_| Error::NotFound)?;
|
||||||
|
for entry in search.0 {
|
||||||
|
let entry = SearchEntry::construct(entry);
|
||||||
|
let email = entry.attrs.get("mail").and_then(|vec| vec.first());
|
||||||
|
if let Some(email) = email {
|
||||||
|
let _ = ldap_conn.unbind();
|
||||||
|
return NewUser::new_local(
|
||||||
|
conn,
|
||||||
|
name.to_owned(),
|
||||||
|
name.to_owned(),
|
||||||
|
Role::Normal,
|
||||||
|
"",
|
||||||
|
email.to_owned(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = ldap_conn.unbind();
|
||||||
|
Err(Error::NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ldap_login(&self, password: &str) -> bool {
|
||||||
|
if let Some(ldap) = CONFIG.ldap.as_ref() {
|
||||||
|
let mut conn = if let Ok(conn) = LdapConn::new(&ldap.addr) {
|
||||||
|
conn
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let name = format!(
|
||||||
|
"{}={},{}",
|
||||||
|
ldap.user_name_attr, &self.username, ldap.base_dn
|
||||||
|
);
|
||||||
|
if let Ok(bind) = conn.simple_bind(&name, password) {
|
||||||
|
bind.success().is_ok()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn login(conn: &Connection, ident: &str, password: &str) -> Result<User> {
|
||||||
|
let local_id = Instance::get_local()?.id;
|
||||||
|
let user = match User::find_by_email(conn, ident) {
|
||||||
|
Ok(user) => Ok(user),
|
||||||
|
_ => User::find_by_name(conn, ident, local_id),
|
||||||
|
}
|
||||||
|
.and_then(|u| {
|
||||||
|
if u.instance_id == local_id {
|
||||||
|
Ok(u)
|
||||||
|
} else {
|
||||||
|
Err(Error::NotFound)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
match user {
|
||||||
|
Ok(user) if user.hashed_password.is_some() => {
|
||||||
|
if bcrypt::verify(password, user.hashed_password.as_ref().unwrap()).unwrap_or(false)
|
||||||
|
{
|
||||||
|
Ok(user)
|
||||||
|
} else {
|
||||||
|
Err(Error::NotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(user) => {
|
||||||
|
if user.ldap_login(password) {
|
||||||
|
Ok(user)
|
||||||
|
} else {
|
||||||
|
Err(Error::NotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e => {
|
||||||
|
if let Ok(user) = User::ldap_register(conn, ident, password) {
|
||||||
|
return Ok(user);
|
||||||
|
}
|
||||||
|
// if no user was found, and we were unable to auto-register from ldap
|
||||||
|
// fake-verify a password, and return an error.
|
||||||
|
let other = User::get(&*conn, 1)
|
||||||
|
.expect("No user is registered")
|
||||||
|
.hashed_password;
|
||||||
|
other.map(|pass| bcrypt::verify(password, &pass));
|
||||||
|
e
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_password(&self, conn: &Connection, pass: &str) -> Result<()> {
|
pub fn reset_password(&self, conn: &Connection, pass: &str) -> Result<()> {
|
||||||
@ -983,7 +1089,7 @@ impl NewUser {
|
|||||||
role: Role,
|
role: Role,
|
||||||
summary: &str,
|
summary: &str,
|
||||||
email: String,
|
email: String,
|
||||||
password: String,
|
password: Option<String>,
|
||||||
) -> Result<User> {
|
) -> Result<User> {
|
||||||
let (pub_key, priv_key) = gen_keypair();
|
let (pub_key, priv_key) = gen_keypair();
|
||||||
let instance = Instance::get_local()?;
|
let instance = Instance::get_local()?;
|
||||||
@ -1001,7 +1107,7 @@ impl NewUser {
|
|||||||
summary: summary.to_owned(),
|
summary: summary.to_owned(),
|
||||||
summary_html: SafeString::new(&utils::md_to_html(&summary, None, false, None).0),
|
summary_html: SafeString::new(&utils::md_to_html(&summary, None, false, None).0),
|
||||||
email: Some(email),
|
email: Some(email),
|
||||||
hashed_password: Some(password),
|
hashed_password: password,
|
||||||
instance_id: instance.id,
|
instance_id: instance.id,
|
||||||
public_key: String::from_utf8(pub_key).or(Err(Error::Signature))?,
|
public_key: String::from_utf8(pub_key).or(Err(Error::Signature))?,
|
||||||
private_key: Some(String::from_utf8(priv_key).or(Err(Error::Signature))?),
|
private_key: Some(String::from_utf8(priv_key).or(Err(Error::Signature))?),
|
||||||
@ -1043,7 +1149,7 @@ pub(crate) mod tests {
|
|||||||
Role::Admin,
|
Role::Admin,
|
||||||
"Hello there, I'm the admin",
|
"Hello there, I'm the admin",
|
||||||
"admin@example.com".to_owned(),
|
"admin@example.com".to_owned(),
|
||||||
"invalid_admin_password".to_owned(),
|
Some("invalid_admin_password".to_owned()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let user = NewUser::new_local(
|
let user = NewUser::new_local(
|
||||||
@ -1053,7 +1159,7 @@ pub(crate) mod tests {
|
|||||||
Role::Normal,
|
Role::Normal,
|
||||||
"Hello there, I'm no one",
|
"Hello there, I'm no one",
|
||||||
"user@example.com".to_owned(),
|
"user@example.com".to_owned(),
|
||||||
"invalid_user_password".to_owned(),
|
Some("invalid_user_password".to_owned()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let other = NewUser::new_local(
|
let other = NewUser::new_local(
|
||||||
@ -1063,7 +1169,7 @@ pub(crate) mod tests {
|
|||||||
Role::Normal,
|
Role::Normal,
|
||||||
"Hello there, I'm someone else",
|
"Hello there, I'm someone else",
|
||||||
"other@example.com".to_owned(),
|
"other@example.com".to_owned(),
|
||||||
"invalid_other_password".to_owned(),
|
Some("invalid_other_password".to_owned()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
vec![admin, user, other]
|
vec![admin, user, other]
|
||||||
@ -1082,7 +1188,7 @@ pub(crate) mod tests {
|
|||||||
Role::Normal,
|
Role::Normal,
|
||||||
"Hello I'm a test",
|
"Hello I'm a test",
|
||||||
"test@example.com".to_owned(),
|
"test@example.com".to_owned(),
|
||||||
User::hash_pass("test_password").unwrap(),
|
Some(User::hash_pass("test_password").unwrap()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -1165,12 +1271,15 @@ pub(crate) mod tests {
|
|||||||
Role::Normal,
|
Role::Normal,
|
||||||
"Hello I'm a test",
|
"Hello I'm a test",
|
||||||
"test@example.com".to_owned(),
|
"test@example.com".to_owned(),
|
||||||
User::hash_pass("test_password").unwrap(),
|
Some(User::hash_pass("test_password").unwrap()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(test_user.auth("test_password"));
|
assert_eq!(
|
||||||
assert!(!test_user.auth("other_password"));
|
User::login(conn, "test", "test_password").unwrap().id,
|
||||||
|
test_user.id
|
||||||
|
);
|
||||||
|
assert!(User::login(conn, "test", "other_password").is_err());
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
17
release.toml
Normal file
17
release.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# we don't have a crate yet, so
|
||||||
|
disable-publish = true
|
||||||
|
# change when we all have gpg keys
|
||||||
|
sign-commit = false
|
||||||
|
dev-version-ext = 'dev'
|
||||||
|
# update all crates in plume at once:
|
||||||
|
consolidate-commits = true
|
||||||
|
|
||||||
|
pre-release-hook = ["crowdin", "pull"]
|
||||||
|
|
||||||
|
pre-release-replacements = [
|
||||||
|
{file="CHANGELOG.md", search="Unreleased", replace="[{{version}}]"},
|
||||||
|
{file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1},
|
||||||
|
{file="CHANGELOG.md", search="No release date", replace="{{date}}"},
|
||||||
|
{file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n\n## [Unreleased] - ReleaseDate", exactly=1},
|
||||||
|
{file="CHANGELOG.md", search="<!-- next-url -->", replace="<!-- next-url -->\n[Unreleased]: https://github.com/Plume-org/Plume/compare/{{tag_name}}...HEAD", exactly=1},
|
||||||
|
]
|
@ -62,30 +62,20 @@ pub fn oauth(
|
|||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let app = App::find_by_client_id(conn, &query.client_id)?;
|
let app = App::find_by_client_id(conn, &query.client_id)?;
|
||||||
if app.client_secret == query.client_secret {
|
if app.client_secret == query.client_secret {
|
||||||
if let Ok(user) = User::find_by_fqn(&rockets, &query.username) {
|
if let Ok(user) = User::login(conn, &query.username, &query.password) {
|
||||||
if user.auth(&query.password) {
|
let token = ApiToken::insert(
|
||||||
let token = ApiToken::insert(
|
conn,
|
||||||
conn,
|
NewApiToken {
|
||||||
NewApiToken {
|
app_id: app.id,
|
||||||
app_id: app.id,
|
user_id: user.id,
|
||||||
user_id: user.id,
|
value: random_hex(),
|
||||||
value: random_hex(),
|
scopes: query.scopes.clone(),
|
||||||
scopes: query.scopes.clone(),
|
},
|
||||||
},
|
)?;
|
||||||
)?;
|
Ok(Json(json!({
|
||||||
Ok(Json(json!({
|
"token": token.value
|
||||||
"token": token.value
|
})))
|
||||||
})))
|
|
||||||
} else {
|
|
||||||
Ok(Json(json!({
|
|
||||||
"error": "Invalid credentials"
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Making fake password verification to avoid different
|
|
||||||
// response times that would make it possible to know
|
|
||||||
// if a username is registered or not.
|
|
||||||
User::get(conn, 1)?.auth(&query.password);
|
|
||||||
Ok(Json(json!({
|
Ok(Json(json!({
|
||||||
"error": "Invalid credentials"
|
"error": "Invalid credentials"
|
||||||
})))
|
})))
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use heck::{CamelCase, KebabCase};
|
use heck::KebabCase;
|
||||||
use rocket_contrib::json::Json;
|
use rocket_contrib::json::Json;
|
||||||
|
|
||||||
use crate::api::{authorization::*, Api};
|
use crate::api::{authorization::*, Api};
|
||||||
@ -181,7 +181,7 @@ pub fn create(
|
|||||||
Tag::insert(
|
Tag::insert(
|
||||||
conn,
|
conn,
|
||||||
NewTag {
|
NewTag {
|
||||||
tag: hashtag.to_camel_case(),
|
tag: hashtag,
|
||||||
is_hashtag: true,
|
is_hashtag: true,
|
||||||
post_id: post.id,
|
post_id: post.id,
|
||||||
},
|
},
|
||||||
|
27
src/main.rs
Normal file → Executable file
27
src/main.rs
Normal file → Executable file
@ -10,6 +10,7 @@ extern crate serde_json;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate validator_derive;
|
extern crate validator_derive;
|
||||||
|
|
||||||
|
use chrono::Utc;
|
||||||
use clap::App;
|
use clap::App;
|
||||||
use diesel::r2d2::ConnectionManager;
|
use diesel::r2d2::ConnectionManager;
|
||||||
use plume_models::{
|
use plume_models::{
|
||||||
@ -21,6 +22,8 @@ use plume_models::{
|
|||||||
};
|
};
|
||||||
use rocket_csrf::CsrfFairingBuilder;
|
use rocket_csrf::CsrfFairingBuilder;
|
||||||
use scheduled_thread_pool::ScheduledThreadPool;
|
use scheduled_thread_pool::ScheduledThreadPool;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@ -98,8 +101,30 @@ Then try to restart Plume.
|
|||||||
}
|
}
|
||||||
let workpool = ScheduledThreadPool::with_name("worker {}", num_cpus::get());
|
let workpool = ScheduledThreadPool::with_name("worker {}", num_cpus::get());
|
||||||
// we want a fast exit here, so
|
// we want a fast exit here, so
|
||||||
|
let mut open_searcher =
|
||||||
|
UnmanagedSearcher::open(&CONFIG.search_index, &CONFIG.search_tokenizers);
|
||||||
|
if let Err(Error::Search(SearcherError::InvalidIndexDataError)) = open_searcher {
|
||||||
|
if UnmanagedSearcher::create(&CONFIG.search_index, &CONFIG.search_tokenizers).is_err() {
|
||||||
|
let current_path = Path::new(&CONFIG.search_index);
|
||||||
|
let backup_path = format!("{}.{}", ¤t_path.display(), Utc::now().timestamp());
|
||||||
|
let backup_path = Path::new(&backup_path);
|
||||||
|
fs::rename(current_path, backup_path)
|
||||||
|
.expect("main: error on backing up search index directory for recreating");
|
||||||
|
if UnmanagedSearcher::create(&CONFIG.search_index, &CONFIG.search_tokenizers).is_ok() {
|
||||||
|
if fs::remove_dir_all(backup_path).is_err() {
|
||||||
|
eprintln!(
|
||||||
|
"error on removing backup directory: {}. it remains",
|
||||||
|
backup_path.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("main: error on recreating search index in new index format. remove search index and run `plm search init` manually");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open_searcher = UnmanagedSearcher::open(&CONFIG.search_index, &CONFIG.search_tokenizers);
|
||||||
|
}
|
||||||
#[allow(clippy::match_wild_err_arm)]
|
#[allow(clippy::match_wild_err_arm)]
|
||||||
let searcher = match UnmanagedSearcher::open(&CONFIG.search_index, &CONFIG.search_tokenizers) {
|
let searcher = match open_searcher {
|
||||||
Err(Error::Search(e)) => match e {
|
Err(Error::Search(e)) => match e {
|
||||||
SearcherError::WriteLockAcquisitionError => panic!(
|
SearcherError::WriteLockAcquisitionError => panic!(
|
||||||
r#"
|
r#"
|
||||||
|
@ -210,11 +210,19 @@ pub fn add_email_blocklist(
|
|||||||
form: LenientForm<NewBlocklistedEmail>,
|
form: LenientForm<NewBlocklistedEmail>,
|
||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> Result<Flash<Redirect>, ErrorPage> {
|
) -> Result<Flash<Redirect>, ErrorPage> {
|
||||||
BlocklistedEmail::insert(&*rockets.conn, form.0)?;
|
let result = BlocklistedEmail::insert(&*rockets.conn, form.0);
|
||||||
Ok(Flash::success(
|
|
||||||
Redirect::to(uri!(admin_email_blocklist: page = None)),
|
if let Err(Error::Db(_)) = result {
|
||||||
i18n!(rockets.intl.catalog, "Email Blocked"),
|
Ok(Flash::error(
|
||||||
))
|
Redirect::to(uri!(admin_email_blocklist: page = None)),
|
||||||
|
i18n!(rockets.intl.catalog, "Email already blocked"),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(Flash::success(
|
||||||
|
Redirect::to(uri!(admin_email_blocklist: page = None)),
|
||||||
|
i18n!(rockets.intl.catalog, "Email Blocked"),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[get("/admin/emails?<page>")]
|
#[get("/admin/emails?<page>")]
|
||||||
pub fn admin_email_blocklist(
|
pub fn admin_email_blocklist(
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use heck::{CamelCase, KebabCase};
|
use heck::KebabCase;
|
||||||
use rocket::request::LenientForm;
|
use rocket::request::LenientForm;
|
||||||
use rocket::response::{Flash, Redirect};
|
use rocket::response::{Flash, Redirect};
|
||||||
use rocket_i18n::I18n;
|
use rocket_i18n::I18n;
|
||||||
@ -314,18 +314,17 @@ pub fn update(
|
|||||||
let tags = form
|
let tags = form
|
||||||
.tags
|
.tags
|
||||||
.split(',')
|
.split(',')
|
||||||
.map(|t| t.trim().to_camel_case())
|
.map(|t| t.trim())
|
||||||
.filter(|t| !t.is_empty())
|
.filter(|t| !t.is_empty())
|
||||||
.collect::<HashSet<_>>()
|
.collect::<HashSet<_>>()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|t| Tag::build_activity(t).ok())
|
.filter_map(|t| Tag::build_activity(t.to_string()).ok())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
post.update_tags(&conn, tags)
|
post.update_tags(&conn, tags)
|
||||||
.expect("post::update: tags error");
|
.expect("post::update: tags error");
|
||||||
|
|
||||||
let hashtags = hashtags
|
let hashtags = hashtags
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|h| h.to_camel_case())
|
|
||||||
.collect::<HashSet<_>>()
|
.collect::<HashSet<_>>()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|t| Tag::build_activity(t).ok())
|
.filter_map(|t| Tag::build_activity(t).ok())
|
||||||
@ -489,14 +488,14 @@ pub fn create(
|
|||||||
let tags = form
|
let tags = form
|
||||||
.tags
|
.tags
|
||||||
.split(',')
|
.split(',')
|
||||||
.map(|t| t.trim().to_camel_case())
|
.map(|t| t.trim())
|
||||||
.filter(|t| !t.is_empty())
|
.filter(|t| !t.is_empty())
|
||||||
.collect::<HashSet<_>>();
|
.collect::<HashSet<_>>();
|
||||||
for tag in tags {
|
for tag in tags {
|
||||||
Tag::insert(
|
Tag::insert(
|
||||||
&*conn,
|
&*conn,
|
||||||
NewTag {
|
NewTag {
|
||||||
tag,
|
tag: tag.to_string(),
|
||||||
is_hashtag: false,
|
is_hashtag: false,
|
||||||
post_id: post.id,
|
post_id: post.id,
|
||||||
},
|
},
|
||||||
@ -507,7 +506,7 @@ pub fn create(
|
|||||||
Tag::insert(
|
Tag::insert(
|
||||||
&*conn,
|
&*conn,
|
||||||
NewTag {
|
NewTag {
|
||||||
tag: hashtag.to_camel_case(),
|
tag: hashtag,
|
||||||
is_hashtag: true,
|
is_hashtag: true,
|
||||||
post_id: post.id,
|
post_id: post.id,
|
||||||
},
|
},
|
||||||
|
@ -48,38 +48,19 @@ pub fn create(
|
|||||||
rockets: PlumeRocket,
|
rockets: PlumeRocket,
|
||||||
) -> RespondOrRedirect {
|
) -> RespondOrRedirect {
|
||||||
let conn = &*rockets.conn;
|
let conn = &*rockets.conn;
|
||||||
let user = User::find_by_email(&*conn, &form.email_or_name)
|
|
||||||
.or_else(|_| User::find_by_fqn(&rockets, &form.email_or_name));
|
|
||||||
let mut errors = match form.validate() {
|
let mut errors = match form.validate() {
|
||||||
Ok(_) => ValidationErrors::new(),
|
Ok(_) => ValidationErrors::new(),
|
||||||
Err(e) => e,
|
Err(e) => e,
|
||||||
};
|
};
|
||||||
|
let user = User::login(conn, &form.email_or_name, &form.password);
|
||||||
let user_id = if let Ok(user) = user {
|
let user_id = if let Ok(user) = user {
|
||||||
if !user.auth(&form.password) {
|
user.id.to_string()
|
||||||
let mut err = ValidationError::new("invalid_login");
|
|
||||||
err.message = Some(Cow::from("Invalid username, or password"));
|
|
||||||
errors.add("email_or_name", err);
|
|
||||||
String::new()
|
|
||||||
} else {
|
|
||||||
user.id.to_string()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Fake password verification, only to avoid different login times
|
|
||||||
// that could be used to see if an email adress is registered or not
|
|
||||||
User::get(&*conn, 1)
|
|
||||||
.map(|u| u.auth(&form.password))
|
|
||||||
.expect("No user is registered");
|
|
||||||
|
|
||||||
let mut err = ValidationError::new("invalid_login");
|
let mut err = ValidationError::new("invalid_login");
|
||||||
err.message = Some(Cow::from("Invalid username, or password"));
|
err.message = Some(Cow::from("Invalid username, or password"));
|
||||||
errors.add("email_or_name", err);
|
errors.add("email_or_name", err);
|
||||||
String::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
if !errors.is_empty() {
|
|
||||||
return render!(session::login(&rockets.to_context(), None, &*form, errors)).into();
|
return render!(session::login(&rockets.to_context(), None, &*form, errors)).into();
|
||||||
}
|
};
|
||||||
|
|
||||||
cookies.add_private(
|
cookies.add_private(
|
||||||
Cookie::build(AUTH_COOKIE, user_id)
|
Cookie::build(AUTH_COOKIE, user_id)
|
||||||
|
@ -541,7 +541,7 @@ pub fn create(
|
|||||||
Role::Normal,
|
Role::Normal,
|
||||||
"",
|
"",
|
||||||
form.email.to_string(),
|
form.email.to_string(),
|
||||||
User::hash_pass(&form.password).map_err(to_validation)?,
|
Some(User::hash_pass(&form.password).map_err(to_validation)?),
|
||||||
).map_err(to_validation)?;
|
).map_err(to_validation)?;
|
||||||
Ok(Flash::success(
|
Ok(Flash::success(
|
||||||
Redirect::to(uri!(super::session::new: m = _)),
|
Redirect::to(uri!(super::session::new: m = _)),
|
||||||
|
@ -10,9 +10,6 @@
|
|||||||
@:base(ctx, tl.name.clone(), {}, {}, {
|
@:base(ctx, tl.name.clone(), {}, {}, {
|
||||||
<section class="flex wrap" dir="auto">
|
<section class="flex wrap" dir="auto">
|
||||||
<h1 class="grow">@i18n_timeline_name(ctx.1, &tl.name)</h1>
|
<h1 class="grow">@i18n_timeline_name(ctx.1, &tl.name)</h1>
|
||||||
@if ctx.clone().2.map(|u| (u.is_admin() && tl.user_id.is_none()) || Some(u.id) == tl.user_id).unwrap_or(false) {
|
|
||||||
<a href="@uri!(timelines::edit: _id = tl.id)" class="button inline-block">@i18n!(ctx.1, "Edit")</a>
|
|
||||||
}
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@tabs(&vec![(format!("{}", uri!(instance::index)), i18n!(ctx.1, "Latest articles"), false)]
|
@tabs(&vec![(format!("{}", uri!(instance::index)), i18n!(ctx.1, "Latest articles"), false)]
|
||||||
|
Loading…
Reference in New Issue
Block a user