Compare commits

...

716 Commits

Author SHA1 Message Date
Matt Baer 038a80c25e
Merge pull request #893 from writefreely/consistent-reader-nav
Fix Admin and Invite links never showing on Reader nav
2024-04-17 12:44:28 -04:00
Matt Baer 9ece6682ef
Merge pull request #930 from tkngaejcpi/develop
support more image formats
2024-04-17 12:43:50 -04:00
Matt Baer 41e1989345
Merge pull request #982 from writefreely/dependabot/go_modules/golang.org/x/net-0.22.0
Bump golang.org/x/net from 0.20.0 to 0.22.0
2024-04-03 14:25:17 -04:00
Matt Baer 34d902062f
Merge pull request #927 from writefreely/dependabot/go_modules/github.com/stretchr/testify-1.9.0
Bump github.com/stretchr/testify from 1.8.4 to 1.9.0
2024-04-03 14:23:13 -04:00
dependabot[bot] ed9ff51b68
Bump golang.org/x/net from 0.20.0 to 0.22.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.20.0 to 0.22.0.
- [Commits](https://github.com/golang/net/compare/v0.20.0...v0.22.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 22:46:41 +00:00
Riley Chang 83ffea7fa0
support more image formats 2024-03-04 22:48:51 +08:00
dependabot[bot] 3dd0a9b8dc
Bump github.com/stretchr/testify from 1.8.4 to 1.9.0
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.4 to 1.9.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.4...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-01 22:40:53 +00:00
Matt Baer 427f4980b9
Merge pull request #874 from claabs/docker-fixes
Version number and healthcheck fixes for Docker image
2024-02-20 10:19:30 -05:00
Matt Baer e34a58d0ef Fix Admin and Invite links never showing on Reader nav 2024-02-20 10:02:14 -05:00
Matt Baer 6d547040ef
Merge pull request #878 from c7io-dev/snullp-patch-1
Add "Import posts" to base.tmpl to be consistent with /me/* nav bar
2024-02-20 09:55:00 -05:00
Matt Baer 216f36f47b
Merge pull request #883 from elkcityhazard/develop
add f.created to join, add Created to Scan
2024-02-20 09:32:20 -05:00
Andrew M McCall a352a3518a add f.created to join, add Created to Scan 2024-02-14 19:48:47 -05:00
Big Squirrel 1a3f3f0ec6
Add "Import posts" to base.tmpl to be consistent with /me/* nav bar 2024-02-09 12:51:00 -08:00
charlocharlie 306ca173c6 Include .git context in Docker build for UI version number
Fixes #873
2024-02-06 14:31:18 -06:00
Matt Baer 22de459a72
Merge pull request #854 from writefreely/api-inconsistencies
Fix Collection property serialization on API
2024-02-02 09:01:57 -05:00
Matt Baer 5be1f2451c Bump version to 0.15 2024-02-02 14:50:48 +01:00
Matt Baer 3a53353ed8
Merge pull request #861 from writefreely/dependabot/go_modules/github.com/mattn/go-sqlite3-1.14.21
Bump github.com/mattn/go-sqlite3 from 1.14.19 to 1.14.21
2024-02-01 18:53:18 -05:00
dependabot[bot] 56cad35b19
Bump github.com/mattn/go-sqlite3 from 1.14.19 to 1.14.21
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.19 to 1.14.21.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.19...v1.14.21)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-sqlite3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-01 22:40:03 +00:00
Matt Baer ff84c7aa4d
Merge pull request #826 from andi1984/issue-612-rtl
fix: RTL support on post textarea
2024-02-01 08:29:18 -05:00
Andreas Sander 4c6169d55d fix: RTL support on post textarea
Fixing right to left (short: RTL) support for respective RTL languages
by adding auto-detection for the user content's directionality based on
the text's language.

Fixes #612
2024-01-10 23:30:04 +01:00
Matt Baer ab1b2922cc
Merge pull request #856 from writefreely/dependabot/go_modules/golang.org/x/net-0.20.0
Bump golang.org/x/net from 0.17.0 to 0.20.0
2024-01-10 16:13:39 -05:00
dependabot[bot] 9401d047d6
Bump golang.org/x/net from 0.17.0 to 0.20.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.20.0.
- [Commits](https://github.com/golang/net/compare/v0.17.0...v0.20.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-10 21:13:04 +00:00
Matt Baer 54b46b61db
Merge pull request #855 from writefreely/dependabot/go_modules/golang.org/x/crypto-0.18.0
Bump golang.org/x/crypto from 0.14.0 to 0.18.0
2024-01-10 16:12:18 -05:00
Matt Baer 235a3ee143
Merge pull request #849 from writefreely/dependabot/go_modules/github.com/urfave/cli/v2-2.27.1
Bump github.com/urfave/cli/v2 from 2.25.7 to 2.27.1
2024-01-10 16:11:41 -05:00
Matt Baer f4accd5064
Merge pull request #848 from writefreely/dependabot/go_modules/github.com/mattn/go-sqlite3-1.14.19
Bump github.com/mattn/go-sqlite3 from 1.14.17 to 1.14.19
2024-01-10 16:10:37 -05:00
Matt Baer bc00ae1963
Merge pull request #841 from writefreely/dependabot/go_modules/github.com/gorilla/schema-1.2.1
Bump github.com/gorilla/schema from 1.2.0 to 1.2.1
2024-01-10 16:09:38 -05:00
dependabot[bot] 775d86cb00
Bump github.com/gorilla/schema from 1.2.0 to 1.2.1
Bumps [github.com/gorilla/schema](https://github.com/gorilla/schema) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/gorilla/schema/releases)
- [Commits](https://github.com/gorilla/schema/compare/v1.2.0...v1.2.1)

---
updated-dependencies:
- dependency-name: github.com/gorilla/schema
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-10 21:08:38 +00:00
Matt Baer 90e564870d
Merge pull request #838 from writefreely/dependabot/go_modules/github.com/gorilla/feeds-1.1.2
Bump github.com/gorilla/feeds from 1.1.1 to 1.1.2
2024-01-10 16:06:24 -05:00
dependabot[bot] 62c26e78ba
Bump github.com/gorilla/feeds from 1.1.1 to 1.1.2
Bumps [github.com/gorilla/feeds](https://github.com/gorilla/feeds) from 1.1.1 to 1.1.2.
- [Release notes](https://github.com/gorilla/feeds/releases)
- [Commits](https://github.com/gorilla/feeds/compare/v1.1.1...v1.1.2)

---
updated-dependencies:
- dependency-name: github.com/gorilla/feeds
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-10 21:04:53 +00:00
dependabot[bot] 69002fdcbf
Bump golang.org/x/crypto from 0.14.0 to 0.18.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.18.0.
- [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.18.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-10 21:04:33 +00:00
dependabot[bot] 4acf08d9e9
Bump github.com/mattn/go-sqlite3 from 1.14.17 to 1.14.19
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.17 to 1.14.19.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.17...v1.14.19)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-sqlite3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-10 21:04:27 +00:00
Matt Baer df7fee2018
Merge pull request #837 from writefreely/dependabot/go_modules/github.com/fatih/color-1.16.0
Bump github.com/fatih/color from 1.15.0 to 1.16.0
2024-01-10 16:03:54 -05:00
Matt Baer c64c7c77ae
Merge pull request #836 from writefreely/dependabot/go_modules/github.com/gorilla/sessions-1.2.2
Bump github.com/gorilla/sessions from 1.2.1 to 1.2.2
2024-01-10 16:03:40 -05:00
dependabot[bot] e788b90b04
Bump github.com/gorilla/sessions from 1.2.1 to 1.2.2
Bumps [github.com/gorilla/sessions](https://github.com/gorilla/sessions) from 1.2.1 to 1.2.2.
- [Release notes](https://github.com/gorilla/sessions/releases)
- [Commits](https://github.com/gorilla/sessions/compare/v1.2.1...v1.2.2)

---
updated-dependencies:
- dependency-name: github.com/gorilla/sessions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-10 21:02:58 +00:00
Matt Baer 66f049cc39
Merge pull request #834 from writefreely/dependabot/go_modules/github.com/gorilla/mux-1.8.1
Bump github.com/gorilla/mux from 1.8.0 to 1.8.1
2024-01-10 16:01:06 -05:00
Matt Baer ff07c447ee
Merge pull request #833 from writefreely/dependabot/go_modules/github.com/gorilla/csrf-1.7.2
Bump github.com/gorilla/csrf from 1.7.1 to 1.7.2
2024-01-10 16:00:16 -05:00
Matt Baer d33a556732
Merge pull request #823 from writefreely/contact-links
Add Contact page links to footers
2024-01-10 15:57:49 -05:00
Matt Baer 737d76176a Fix indentation in footer.tmpl 2024-01-10 15:57:31 -05:00
dependabot[bot] 8e6ddc1993
Bump github.com/urfave/cli/v2 from 2.25.7 to 2.27.1
Bumps [github.com/urfave/cli/v2](https://github.com/urfave/cli) from 2.25.7 to 2.27.1.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v2.25.7...v2.27.1)

---
updated-dependencies:
- dependency-name: github.com/urfave/cli/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-01 22:29:53 +00:00
Matt Baer b85afa1ea6
Merge pull request #828 from d4rklynk/dockerfile
Dockerfile
2023-12-01 17:49:54 -05:00
dependabot[bot] 6b8cc591cc
Bump github.com/fatih/color from 1.15.0 to 1.16.0
Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.15.0 to 1.16.0.
- [Release notes](https://github.com/fatih/color/releases)
- [Commits](https://github.com/fatih/color/compare/v1.15.0...v1.16.0)

---
updated-dependencies:
- dependency-name: github.com/fatih/color
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-01 22:47:13 +00:00
dependabot[bot] 859a4b37e5
Bump github.com/gorilla/mux from 1.8.0 to 1.8.1
Bumps [github.com/gorilla/mux](https://github.com/gorilla/mux) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/gorilla/mux/releases)
- [Commits](https://github.com/gorilla/mux/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/gorilla/mux
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-01 22:46:48 +00:00
dependabot[bot] 3caa33b9bf
Bump github.com/gorilla/csrf from 1.7.1 to 1.7.2
Bumps [github.com/gorilla/csrf](https://github.com/gorilla/csrf) from 1.7.1 to 1.7.2.
- [Release notes](https://github.com/gorilla/csrf/releases)
- [Commits](https://github.com/gorilla/csrf/compare/v1.7.1...v1.7.2)

---
updated-dependencies:
- dependency-name: github.com/gorilla/csrf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-01 22:46:41 +00:00
Matt Baer e932467ac9
Merge pull request #822 from writefreely/custom-css-config
Look for custom CSS in static_parent_dir
2023-12-01 17:34:03 -05:00
d4rklynk aac4514577 Fix healthcheck URL 2023-11-18 14:30:54 +01:00
d4rklynk 21f5073717 Fix port 2023-11-18 14:24:37 +01:00
d4rklynk 64d1a2f536 Update Dockerfile 2023-11-18 14:18:39 +01:00
Matt Baer e4e059cb13 Fix Collection property serialization on API
Use standard string instead of sql.NullString for `style_sheet`, `script`, and `signature`.

Addresses #820
2023-11-07 10:54:16 -05:00
Matt Baer feab841609 Add Contact page links to footers 2023-11-07 10:21:24 -05:00
Matt Baer 3e7d236c6d
Merge pull request #528 from isaacsu/protect-drafts
Protect drafts if they are part of a Private or Protected collection
2023-11-07 10:12:19 -05:00
Matt Baer 289730e24a Look for custom CSS in static_parent_dir
Previously, it would only check the current directory instead of using the configured
`static_parent_dir`. This fixes that.

Closes #792
2023-11-07 09:06:50 -05:00
Matt Baer a1becfdc83
Merge pull request #799 from heyakyra/twitter-card-fix-large-preview
Conditionally use twitter large summary card format when an image is available
2023-11-06 16:31:10 -05:00
Matt Baer 0bf0b425ee
Merge pull request #811 from writefreely/dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.26
Bump github.com/microcosm-cc/bluemonday from 1.0.25 to 1.0.26
2023-11-02 13:40:15 -04:00
dependabot[bot] 10994c532f
Bump github.com/microcosm-cc/bluemonday from 1.0.25 to 1.0.26
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.25 to 1.0.26.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.25...v1.0.26)

---
updated-dependencies:
- dependency-name: github.com/microcosm-cc/bluemonday
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-02 17:15:20 +00:00
Matt Baer ae70c2dbe4
Merge pull request #810 from writefreely/dependabot/go_modules/golang.org/x/net-0.17.0
Bump golang.org/x/net from 0.15.0 to 0.17.0
2023-11-02 13:14:39 -04:00
dependabot[bot] cdb1ffd1da
Bump golang.org/x/net from 0.15.0 to 0.17.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.15.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.15.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-02 17:12:54 +00:00
Matt Baer d467fdf158
Merge pull request #809 from writefreely/dependabot/go_modules/golang.org/x/crypto-0.14.0
Bump golang.org/x/crypto from 0.13.0 to 0.14.0
2023-11-02 13:11:36 -04:00
dependabot[bot] 643d025381
Bump golang.org/x/crypto from 0.13.0 to 0.14.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.13.0 to 0.14.0.
- [Commits](https://github.com/golang/crypto/compare/v0.13.0...v0.14.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-01 22:45:31 +00:00
Kyra ee485e0488 Conditionally use twitter large summary card format when an image is available. 2023-10-25 17:15:05 -05:00
Matt Baer 5204b3b752
Merge pull request #782 from writefreely/verify-collection-max-lengths
Prevent 500 errors on too-long collection title or description
2023-10-23 12:50:06 -04:00
Matt Baer 45ca9c4c2b
Merge pull request #781 from writefreely/fix-updates-masto
Ensure Update activities work with Mastodon
2023-10-23 12:49:30 -04:00
Matt Baer 71fd25870d
Merge pull request #793 from writefreely/fix-fedi-followers
Add missing methods for showing fediverse followers
2023-10-23 12:48:32 -04:00
Matt Baer dd797c8145 Add missing methods for showing fediverse followers
Fixes #791
2023-10-13 16:45:12 -04:00
Matt Baer 3870749e5e
Merge pull request #785 from blujan/develop
Fix use of NOW() when getting tagged posts
2023-10-11 10:55:51 -04:00
Brennan Lujan 87b3585c44 Fix use of NOW() when getting tagged posts 2023-10-06 20:20:40 -07:00
Matt Baer bf213cd0b0 Fix drafts never showing, even when not part of private/protected blog 2023-10-06 12:40:46 -04:00
Matt Baer 815500ab78 Merge branch 'develop' into protect-drafts 2023-10-06 12:19:37 -04:00
Matt Baer 4aad0338bf
Merge pull request #779 from writefreely/fix-ld-json-response-2
Correctly respond to application/ld+json requests, part 2
2023-10-03 12:06:10 -04:00
Matt Baer 711cb387a5
Merge pull request #778 from writefreely/better-indexing
Add index to improve post retrieval speed on large instances
2023-10-03 12:04:20 -04:00
Matt Baer e3323d11c8
Merge pull request #777 from writefreely/reset-password
Support resetting password via email

Closes T508
2023-10-03 12:03:08 -04:00
Matt Baer 076c4ae2f2 Set blog title maxlength on Customize page 2023-10-03 11:57:42 -04:00
Matt Baer 530a36fc53 Prevent 500 errors on too-long collection title or description
This truncates long titles and descriptions to the maximum column length, so
we don't get errors back from MySQL.

Fixes #600
2023-10-03 11:55:52 -04:00
Matt Baer 8207a25fa9 Tweak style of "Forgot" link on login page 2023-10-03 11:39:41 -04:00
Matt Baer 7b84dafea7 Correctly return on /reset submission when email isn't configured 2023-10-03 11:28:24 -04:00
Matt Baer ed60aea39e Catch and log emailPasswordReset errors 2023-10-03 11:25:05 -04:00
Matt Baer 8f02449ee8 Show friendly message on /reset when password-based login is disabled 2023-10-03 11:19:47 -04:00
Matt Baer 1e37f60d50 Hide "Reset?" link on login page when email disabled 2023-10-03 11:16:11 -04:00
Matt Baer c18987705c Display friendly message on /reset if email is disabled 2023-10-03 11:15:33 -04:00
Matt Baer 7db4b699e2
Merge pull request #776 from writefreely/passwordless-login
Plumbing: login via emailed link

Ref T731
2023-10-03 11:02:30 -04:00
Matt Baer 26ba79ff02
Merge pull request #775 from writefreely/subscriber-insights
Add Subscribers page

Closes T826
2023-10-03 10:59:21 -04:00
Matt Baer b232e7efd7 Fix indentation in subscribers.tmpl 2023-10-03 10:56:23 -04:00
Matt Baer 64dcb56793
Merge pull request #478 from writefreely/letters
Support email subscriptions
2023-10-03 10:50:34 -04:00
Matt Baer 273267343a Ensure Updated property can be omitted
Now, the web-core pkg uses a pointer instead of a var, so we don't send
a zero time.Time value out via ActivityPub.
2023-10-02 21:35:23 -04:00
Matt Baer 27e82f0409
Merge pull request #774 from writefreely/fix-no-fonts
Fix fonts not getting applied on first load
2023-10-02 19:43:50 -04:00
Matt Baer 167971771e Send `updated` parameter with `Update` activities
Per the Mastodon docs, this ensures the activity correctly updates posts there.
https://docs.joinmastodon.org/spec/activitypub/#supported-activities-for-statuses
2023-10-02 19:33:03 -04:00
Matt Baer 2275a288b9 Correctly respond to application/ld+json requests, part 2
This finishes the work started in #766, ensuring that requests to
canonical URLs of blogs and posts (not just at their API endpoints)
respond correctly to `application/ld+json;...` requests.

Fully addresses issue #564
2023-09-26 14:46:35 -04:00
Matt Baer f96f8268f0 Add index to improve post retrieval speed on large instances
On an instance with millions of posts across all users, a single blog with
thousands of posts on it can take a long time to render. This adds an index
to the `posts` table to speed up the basic GetPosts query.

Run: `writefreely db migrate`

Closes #741
2023-09-26 14:36:34 -04:00
Matt Baer 74f3ded250
Merge pull request #545 from clarfonthey/editorconfig
Add editorconfig
2023-09-26 11:52:43 -04:00
Matt Baer c1609cdb90
Merge pull request #658 from jsoref/spelling
Spelling
2023-09-26 11:50:19 -04:00
Matt Baer e96e657430 Fix copyright notices with wrong company name 2023-09-25 19:07:06 -04:00
Matt Baer f404f7b928 Support resetting password via email
This adds a self-serve password reset page. Users can enter their username
and receive an email with a link that will let them create a new password.
If they've never set a password, it will send them a one-time login link
(building on #776) that will then take them to their Account Settings page.
If they don't have an email associated with their account, they'll be
instructed to contact the admin, so they can manually reset the password.

Includes changes to the stylesheet and database, so run:

    make ui
    writefreely db migrate

Closes T508
2023-09-25 18:48:14 -04:00
Matt Baer 7dda53146d Add function for logging in via emailed link
This doesn't add any user-facing behavior, but provides the basic functionality
to generate a one-time use token and email it to a user, so they can log in with
a link instead of a password.
2023-09-25 18:21:20 -04:00
Matt Baer e2fde518ca Fix GetTemporaryOneTimeAccessToken query for SQLite 2023-09-25 18:18:01 -04:00
Matt Baer c75507ca8f Add Subscribers navigation for single-user instances
Ref T826
2023-09-25 17:04:08 -04:00
Matt Baer 82e7dcd3f3 Add Subscribers page
- Shows all fediverse followers and email subscribers
- Shows number of email subscribers on Stats page
- Links to Subscribers page from Stats page

Requires running `make ui` to regenerate stylesheet.

Ref T826
2023-09-25 16:55:57 -04:00
Matt Baer 361c887e2c Revert "use font-display:optional to optimize web font loading"
This reverts commit 059f0d4c54.
2023-09-25 15:58:55 -04:00
Matt Baer 13ca890709
Merge pull request #768 from writefreely/dependabot/go_modules/github.com/writeas/web-core-1.6.0
Bump github.com/writeas/web-core from 1.5.0 to 1.6.0
2023-09-25 15:52:35 -04:00
Matt Baer c6323dba8c Clean up SQLite to-do 2023-09-25 15:38:57 -04:00
Matt Baer dcc6f036c6 Clean up commented-out code 2023-09-25 15:31:31 -04:00
Matt Baer d7d44cb4e1 Catch subscription confirmation email errors 2023-09-25 15:31:10 -04:00
Matt Baer 2a496bd000 Fix subscriber created query for SQLite 2023-09-25 15:30:39 -04:00
Matt Baer 15047b7288 Fix jobs query in SQLite 2023-09-25 15:30:05 -04:00
Matt Baer d1afa44a2e Use standard SetCollectionAttribute method for saving email sub settings 2023-09-25 15:29:23 -04:00
Matt Baer ac40b2f733 Fix publishjobs `id` column in SQLite
Previously, didn't auto-increment or populate
2023-09-25 14:51:28 -04:00
Matt Baer e2b2ba4577 Rename Letters config to Email in collection.tmpl 2023-09-25 14:28:37 -04:00
Matt Baer cc75be1eb5 Rename Letters [letters] config section to Email [email] 2023-09-25 14:26:41 -04:00
Matt Baer 221d0d7dbb Make letters (v13) migration compatible with SQLite 2023-09-25 14:25:24 -04:00
Matt Baer cc9705447d Re-add letters migration 2023-09-25 14:00:18 -04:00
Matt Baer 06968e7341 Merge branch 'develop' into letters 2023-09-25 13:59:46 -04:00
Matt Baer 62f9b2948e Exclude local static files from release build 2023-09-22 17:10:42 -04:00
Matt Baer a8afa18ab2 Bump version to 0.14 2023-09-22 13:12:47 -04:00
Matt Baer b291b89904
Merge pull request #772 from writefreely/better-server-error
Instruct users to contact admin, not WF developers on 500 page
2023-09-22 13:08:20 -04:00
Matt Baer 96eb800eaa
Merge pull request #730 from testwill/loop
chore: slice replace loop
2023-09-22 13:01:36 -04:00
Matt Baer 36f4e30595
Merge pull request #729 from testwill/fmt
chore: unnecessary use of fmt.Sprintf
2023-09-22 13:01:15 -04:00
Matt Baer 177cbf2e57
Merge pull request #728 from testwill/ioutil
chore: remove refs to deprecated io/ioutil
2023-09-22 13:00:41 -04:00
Matt Baer 334d499fb3
Merge pull request #508 from writefreely/lang-posts-filter
Support filtering blog posts by language

Closes T805
2023-09-22 12:53:20 -04:00
Matt Baer 322d0d618a
Merge pull request #771 from writefreely/verification
Support rel=me verification on blogs
2023-09-22 12:47:01 -04:00
Matt Baer c9dc8d5a90 Fix bad copy pasta 2023-09-22 12:46:21 -04:00
Matt Baer d48262a6df Add a customizable Contact page 2023-09-22 12:37:15 -04:00
Matt Baer 83f230ddaf Instruct users to contact admin, not WF devevelopers on 500 page
Misconfigured or broken servers has directed people to the wrong place.

Fixes #684
2023-09-22 12:25:19 -04:00
Matt Baer efe669b874 Remove redundant query on post pages
Previously, we'd call GetCollectionAttribute for the monetization
attribute, when it's already in the collection data.
2023-09-22 11:58:11 -04:00
Matt Baer aa72bcba50 Fix funky comment after gofmt in posts.go 2023-09-22 11:53:57 -04:00
Matt Baer 8626aa12cc Fix post page rendering after rel=me changes
Ref T744
2023-09-22 11:52:14 -04:00
Matt Baer 264bef03b1 Support rel=me verification on blogs
This allows setting a URL, and then renders a <link> element
in the head of the blog. It requires a database migration.

Ref T744
2023-09-21 19:04:34 -04:00
Matt Baer e0c165ff1e Ensure SetCollectionAttribute also updates attributes
Previously, it would only INSERT.
2023-09-21 18:55:48 -04:00
Matt Baer 2986f83121
Merge pull request #770 from writefreely/passwordless-db
Support using MySQL without a password
2023-09-21 18:16:25 -04:00
Matt Baer 3d8b8ecc93 Support using MySQL without a password
Fixes #568
2023-09-21 17:18:15 -04:00
Matt Baer 5d4ebb59c7
Merge pull request #769 from writefreely/fix-mau
Fix monthly active user stats
2023-09-21 17:14:53 -04:00
Matt Baer 2b5318e7a6 Log any database errors when fetching stats
Previously, these errors were simply ignored
2023-09-21 17:08:57 -04:00
Matt Baer baf1d76475 Fix monthly active user stats query 2023-09-21 17:08:17 -04:00
Matt Baer 94bb566e4f
Merge pull request #766 from writefreely/fix-ld-json-response
Correctly respond to application/ld+json requests
2023-09-21 17:03:18 -04:00
Matt Baer d3f312a1e2 Use Chorus template (if configured) when filtering by language
Ref T805
2023-09-21 16:59:32 -04:00
Matt Baer ebeb45ac5a Support pagination when viewing language-filtered posts
Ref T805
2023-09-21 16:58:44 -04:00
Matt Baer 3dc515c249 Merge branch 'develop' into lang-posts-filter 2023-09-21 16:38:48 -04:00
dependabot[bot] 10a415a7ec
Bump github.com/writeas/web-core from 1.5.0 to 1.6.0
Bumps [github.com/writeas/web-core](https://github.com/writeas/web-core) from 1.5.0 to 1.6.0.
- [Commits](https://github.com/writeas/web-core/compare/v1.5.0...v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/writeas/web-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-21 20:36:05 +00:00
Matt Baer a8c5468f65
Merge pull request #767 from writefreely/update-monday
Update writeas/monday to v1.3.0
2023-09-21 16:35:27 -04:00
Matt Baer 43ba111e21 Update writeas/moday to v1.3.0 2023-09-21 16:33:58 -04:00
Matt Baer 299686c13e
Merge pull request #482 from writefreely/rel-nofollow
Remove rel=nofollow attribute in certain situations
2023-09-21 16:26:17 -04:00
Matt Baer dff01a6136
Merge pull request #438 from Dak425/paginate-tag-collection
Add pagination to tag subpages
2023-09-21 16:25:05 -04:00
Matt Baer 8f03da0ec1 Correctly respond to application/ld+json requests
This returns ActivityStreams objects when the Accept header is
`application/ld+json; profile="https://www.w3.org/ns/activitystreams"`,
per the ActivityPub spec.

Fixes #564
2023-09-21 16:16:57 -04:00
Matt Baer 142c5d6cec Re-add ossl_legacy.cnf 2023-09-21 16:07:09 -04:00
Matt Baer 526db318c4 Merge branch 'develop' into letters 2023-09-21 16:03:13 -04:00
Matt Baer fe1f821422
Merge pull request #765 from writefreely/remove-coil
Remove mention of Coil on Customize page
2023-09-19 14:00:48 -04:00
Matt Baer 2fde648519 Remove mention of Coil on Customize page
Fixes #720
2023-09-19 13:41:52 -04:00
Matt Baer 3e21ecb53c
Merge pull request #760 from writefreely/dependabot/github_actions/docker/login-action-3.0.0
Bump docker/login-action from 2.2.0 to 3.0.0
2023-09-19 13:27:59 -04:00
Matt Baer 3ba29aaa2c
Merge pull request #759 from writefreely/dependabot/github_actions/docker/build-push-action-5.0.0
Bump docker/build-push-action from 4.1.1 to 5.0.0
2023-09-19 13:26:15 -04:00
dependabot[bot] c60d135060
Bump docker/login-action from 2.2.0 to 3.0.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 2.2.0 to 3.0.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2.2.0...v3.0.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-12 22:25:40 +00:00
dependabot[bot] 4c48733a3a
Bump docker/build-push-action from 4.1.1 to 5.0.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4.1.1 to 5.0.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4.1.1...v5.0.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-12 22:25:37 +00:00
Matt Baer f2474798bb
Merge pull request #750 from writefreely/dependabot/github_actions/actions/checkout-4
Bump actions/checkout from 3 to 4
2023-09-07 16:19:37 -04:00
Matt Baer 9c9fa8bf62
Merge pull request #753 from writefreely/dependabot/go_modules/golang.org/x/net-0.15.0
Bump golang.org/x/net from 0.13.0 to 0.15.0
2023-09-07 16:18:57 -04:00
dependabot[bot] 3981b6dddb
Bump golang.org/x/net from 0.13.0 to 0.15.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.13.0 to 0.15.0.
- [Commits](https://github.com/golang/net/compare/v0.13.0...v0.15.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-07 20:17:51 +00:00
Matt Baer da3e5d0606
Merge pull request #754 from writefreely/dependabot/go_modules/golang.org/x/crypto-0.13.0
Bump golang.org/x/crypto from 0.12.0 to 0.13.0
2023-09-07 16:16:53 -04:00
Matt Baer 51c46621d8
Merge pull request #752 from lstellway/lstellway/connect-package-to-repo
Adds image labels to connect package with repository
2023-09-07 16:16:23 -04:00
dependabot[bot] 21a1c738d1
Bump golang.org/x/crypto from 0.12.0 to 0.13.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.12.0 to 0.13.0.
- [Commits](https://github.com/golang/crypto/compare/v0.12.0...v0.13.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-07 20:15:51 +00:00
Matt Baer 0814ec28dc
Merge pull request #749 from writefreely/dependabot/go_modules/golang.org/x/crypto-0.12.0
Bump golang.org/x/crypto from 0.11.0 to 0.12.0
2023-09-07 16:14:52 -04:00
lstellway c7729a0432
Adds image labels to connect package with repository 2023-09-07 12:46:45 -07:00
dependabot[bot] a408f0f9ea
Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-04 22:05:03 +00:00
dependabot[bot] e9b03c9350
Bump golang.org/x/crypto from 0.11.0 to 0.12.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.11.0 to 0.12.0.
- [Commits](https://github.com/golang/crypto/compare/v0.11.0...v0.12.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-01 22:47:59 +00:00
Matt Baer 65ec6b44e1
Merge pull request #738 from writefreely/dependabot/go_modules/golang.org/x/net-0.13.0
Bump golang.org/x/net from 0.11.0 to 0.13.0
2023-08-02 11:12:26 -04:00
dependabot[bot] 21efde71f7
Bump golang.org/x/net from 0.11.0 to 0.13.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.11.0 to 0.13.0.
- [Commits](https://github.com/golang/net/compare/v0.11.0...v0.13.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-02 15:10:36 +00:00
Matt Baer 8755f1706c
Merge pull request #736 from writefreely/dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.25
Bump github.com/microcosm-cc/bluemonday from 1.0.24 to 1.0.25
2023-08-02 11:09:24 -04:00
dependabot[bot] 41138e4ab2
Bump github.com/microcosm-cc/bluemonday from 1.0.24 to 1.0.25
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.24 to 1.0.25.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.24...v1.0.25)

---
updated-dependencies:
- dependency-name: github.com/microcosm-cc/bluemonday
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-02 15:06:48 +00:00
Matt Baer 0860d1db1f
Merge pull request #737 from writefreely/dependabot/go_modules/github.com/writeas/web-core-1.5.0
Bump github.com/writeas/web-core from 1.4.1 to 1.5.0
2023-08-02 11:06:04 -04:00
dependabot[bot] b54de10663
Bump github.com/writeas/web-core from 1.4.1 to 1.5.0
Bumps [github.com/writeas/web-core](https://github.com/writeas/web-core) from 1.4.1 to 1.5.0.
- [Commits](https://github.com/writeas/web-core/compare/v1.4.1...v1.5.0)

---
updated-dependencies:
- dependency-name: github.com/writeas/web-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-01 22:57:27 +00:00
guoguangwu 78e59b749b chore: slice replace loop 2023-07-11 12:17:34 +08:00
guoguangwu 20fec65e6b chore: unnecessary use of fmt.Sprintf 2023-07-11 12:10:40 +08:00
guoguangwu cf53730f6c chore: remove refs to deprecated io/ioutil 2023-07-10 17:55:04 +08:00
Matt Baer dbdbcfd100
Merge pull request #618 from Timshel/token
Widen oauth_users.access_token to prevent insertion failure
2023-07-08 00:39:58 -04:00
Matt Baer 54eb2db14d Fix tagged posts falsely showing Older link 2023-07-08 00:31:02 -04:00
Matt Baer e65086b635 Merge branch 'develop' into paginate-tag-collection 2023-07-08 00:01:40 -04:00
Matt Baer b753d41964
Merge pull request #521 from eli-oat/develop
Update 404 error message
2023-07-07 23:57:55 -04:00
Matt Baer 5d5a8536c8
Merge pull request #638 from zer-far/dev
Strip debugging information
2023-07-07 23:51:46 -04:00
Matt Baer 9580cffb3d
Merge pull request #556 from writefreely/dependabot/go_modules/github.com/guregu/null-4.0.0incompatible
Bump github.com/guregu/null from 3.5.0+incompatible to 4.0.0+incompatible
2023-06-30 18:11:44 -04:00
Matt Baer 1aee7ed125
Merge pull request #723 from writefreely/dependabot/go_modules/github.com/urfave/cli/v2-2.25.7
Bump github.com/urfave/cli/v2 from 2.23.5 to 2.25.7
2023-06-30 18:08:31 -04:00
Matt Baer 989d7eb2fc
Merge pull request #705 from writefreely/dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.24
Bump github.com/microcosm-cc/bluemonday from 1.0.23 to 1.0.24
2023-06-30 18:07:52 -04:00
dependabot[bot] ba8aebaa6f
Bump github.com/microcosm-cc/bluemonday from 1.0.23 to 1.0.24
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.23 to 1.0.24.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.23...v1.0.24)

---
updated-dependencies:
- dependency-name: github.com/microcosm-cc/bluemonday
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-30 22:05:07 +00:00
Matt Baer 949f13bf66
Merge pull request #721 from writefreely/dependabot/go_modules/golang.org/x/net-0.11.0
Bump golang.org/x/net from 0.9.0 to 0.11.0
2023-06-30 18:03:47 -04:00
Matt Baer f92f7b13cb
Merge pull request #692 from writefreely/dependabot/go_modules/github.com/go-sql-driver/mysql-1.7.1
Bump github.com/go-sql-driver/mysql from 1.7.0 to 1.7.1
2023-06-30 18:03:20 -04:00
dependabot[bot] 98790ee371
Bump github.com/urfave/cli/v2 from 2.23.5 to 2.25.7
Bumps [github.com/urfave/cli/v2](https://github.com/urfave/cli) from 2.23.5 to 2.25.7.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v2.23.5...v2.25.7)

---
updated-dependencies:
- dependency-name: github.com/urfave/cli/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-30 21:57:55 +00:00
Matt Baer a9733c30cf
Merge pull request #708 from writefreely/dependabot/go_modules/github.com/mattn/go-sqlite3-1.14.17
Bump github.com/mattn/go-sqlite3 from 1.14.16 to 1.14.17
2023-06-30 17:57:48 -04:00
dependabot[bot] d3f935f693
Bump golang.org/x/net from 0.9.0 to 0.11.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.9.0 to 0.11.0.
- [Commits](https://github.com/golang/net/compare/v0.9.0...v0.11.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-30 21:57:43 +00:00
Matt Baer 3eb3146ae9
Merge pull request #709 from writefreely/dependabot/go_modules/github.com/stretchr/testify-1.8.4
Bump github.com/stretchr/testify from 1.8.1 to 1.8.4
2023-06-30 17:57:13 -04:00
Matt Baer 229607a5ab
Merge pull request #711 from writefreely/dependabot/github_actions/docker/login-action-2.2.0
Bump docker/login-action from 2.1.0 to 2.2.0
2023-06-30 17:56:23 -04:00
Matt Baer d476c3b2f7
Merge pull request #716 from writefreely/dependabot/github_actions/docker/metadata-action-4.6.0
Bump docker/metadata-action from 4.1.1 to 4.6.0
2023-06-30 17:55:59 -04:00
Matt Baer 6946d3b785
Merge pull request #717 from writefreely/dependabot/github_actions/docker/build-push-action-4.1.1
Bump docker/build-push-action from 3.2.0 to 4.1.1
2023-06-30 17:53:48 -04:00
Matt Baer e0372979d9
Merge pull request #691 from writefreely/netgo
Use Go DNS resolution in release binaries
2023-06-30 17:53:10 -04:00
dependabot[bot] 639770be4d
Bump docker/build-push-action from 3.2.0 to 4.1.1
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3.2.0 to 4.1.1.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3.2.0...v4.1.1)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 23:00:23 +00:00
dependabot[bot] b0b166e827
Bump docker/metadata-action from 4.1.1 to 4.6.0
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4.1.1 to 4.6.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v4.1.1...v4.6.0)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 23:00:19 +00:00
dependabot[bot] e2237653bb
Bump docker/login-action from 2.1.0 to 2.2.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 2.1.0 to 2.2.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2.1.0...v2.2.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-07 23:00:05 +00:00
dependabot[bot] 77823a382b
Bump github.com/stretchr/testify from 1.8.1 to 1.8.4
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.1 to 1.8.4.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.1...v1.8.4)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-01 22:58:42 +00:00
dependabot[bot] b6d17a9594
Bump github.com/mattn/go-sqlite3 from 1.14.16 to 1.14.17
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.16 to 1.14.17.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.16...v1.14.17)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-sqlite3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-01 22:58:34 +00:00
dependabot[bot] e1e05e5f29
Bump github.com/go-sql-driver/mysql from 1.7.0 to 1.7.1
Bumps [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) from 1.7.0 to 1.7.1.
- [Release notes](https://github.com/go-sql-driver/mysql/releases)
- [Changelog](https://github.com/go-sql-driver/mysql/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-sql-driver/mysql/compare/v1.7.0...v1.7.1)

---
updated-dependencies:
- dependency-name: github.com/go-sql-driver/mysql
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-28 14:20:58 +00:00
Matt Baer 67dbc9b22b
Merge pull request #617 from writefreely/dependabot/go_modules/github.com/go-sql-driver/mysql-1.7.0
Bump github.com/go-sql-driver/mysql from 1.6.0 to 1.7.0
2023-04-28 10:11:32 -04:00
Matt Baer 3f5fd6e2d2
Merge pull request #644 from writefreely/dependabot/go_modules/github.com/dustin/go-humanize-1.0.1
Bump github.com/dustin/go-humanize from 1.0.0 to 1.0.1
2023-04-28 10:10:12 -04:00
Matt Baer 3a7554abe8 Use Go DNS resolution in release binaries
This builds with the `netgo` tag, ensuring WF binaries use Go DNS
resolution instead of libc, preventing unhelpful errors when the
application can't resolve addresses.

Closes #675
2023-04-28 10:07:00 -04:00
Matt Baer e350b7ce8a
Merge pull request #597 from ilteriseroglu-ty/fix-xbuilds
fix Makefile and Dockerfile to build on latest go versions
2023-04-07 12:14:07 -04:00
Matt Baer 1a61128dfc
Merge pull request #621 from writefreely/filepath-errors
Catch and output directory walking errors
2023-04-07 11:47:13 -04:00
Matt Baer ddabab041a
Merge pull request #543 from clarfonthey/unix-support
Add unix socket support
2023-04-07 11:46:08 -04:00
Matt Baer 2ba840634b
Merge pull request #660 from writefreely/fix-alt-text
Loosen restrictions on rendered img alt attribute
2023-04-07 11:26:43 -04:00
Matt Baer ac9c53cfff
Merge pull request #666 from writefreely/dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.23
Bump github.com/microcosm-cc/bluemonday from 1.0.21 to 1.0.23
2023-04-07 11:25:57 -04:00
dependabot[bot] 1a4845aca8
Bump github.com/microcosm-cc/bluemonday from 1.0.21 to 1.0.23
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.21 to 1.0.23.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.21...v1.0.23)

---
updated-dependencies:
- dependency-name: github.com/microcosm-cc/bluemonday
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-07 15:24:24 +00:00
Matt Baer 7c0e69cf41
Merge pull request #672 from writefreely/dependabot/go_modules/golang.org/x/crypto-0.8.0
Bump golang.org/x/crypto from 0.0.0-20200622213623-75b288015ac9 to 0.8.0
2023-04-07 11:23:34 -04:00
dependabot[bot] cdaa13a260
Bump github.com/go-sql-driver/mysql from 1.6.0 to 1.7.0
Bumps [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/go-sql-driver/mysql/releases)
- [Changelog](https://github.com/go-sql-driver/mysql/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-sql-driver/mysql/compare/v1.6.0...v1.7.0)

---
updated-dependencies:
- dependency-name: github.com/go-sql-driver/mysql
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-07 15:22:18 +00:00
dependabot[bot] 0dcfd1809d
Bump github.com/dustin/go-humanize from 1.0.0 to 1.0.1
Bumps [github.com/dustin/go-humanize](https://github.com/dustin/go-humanize) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/dustin/go-humanize/releases)
- [Commits](https://github.com/dustin/go-humanize/compare/v1.0.0...v1.0.1)

---
updated-dependencies:
- dependency-name: github.com/dustin/go-humanize
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-07 15:22:17 +00:00
dependabot[bot] ad6c8f30bc
Bump golang.org/x/crypto from 0.0.0-20200622213623-75b288015ac9 to 0.8.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.0.0-20200622213623-75b288015ac9 to 0.8.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/commits/v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-07 15:22:13 +00:00
Matt Baer 86c76b0442
Merge pull request #670 from writefreely/dependabot/go_modules/github.com/fatih/color-1.15.0
Bump github.com/fatih/color from 1.13.0 to 1.15.0
2023-04-07 11:21:31 -04:00
dependabot[bot] 43176ed7ea
Bump github.com/fatih/color from 1.13.0 to 1.15.0
Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.13.0 to 1.15.0.
- [Release notes](https://github.com/fatih/color/releases)
- [Commits](https://github.com/fatih/color/compare/v1.13.0...v1.15.0)

---
updated-dependencies:
- dependency-name: github.com/fatih/color
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-01 22:59:14 +00:00
Matt Baer 64772aa203 Loosen restrictions on rendered img alt attribute
Previously, certain characters weren't allowed, and they would cause the entire
alt attribute to be elided from the rendered page. Since we safely sanitize the
content of this attribute anyway, this is unnecessary, so we now allow all text
entered there.

Fixes #649
2023-03-14 15:13:03 -04:00
Matt Baer 40b9c08c86
Merge pull request #657 from writefreely/dependabot/go_modules/github.com/writeas/web-core-1.4.1
Bump github.com/writeas/web-core from 1.4.1-0.20220118212728-0da0bcaf018e to 1.4.1
2023-03-07 13:37:34 -05:00
Josh Soref ea81e2c839 spelling: pattern
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:44:02 -05:00
Josh Soref 02fb079a9f spelling: optional
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:44:02 -05:00
Josh Soref 0746ec8567 spelling: modified
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:44:02 -05:00
Josh Soref 7e5d60043d spelling: miscellaneous
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:24:29 -05:00
Josh Soref af875b4d87 spelling: message
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:24:29 -05:00
Josh Soref 8dd7b40c02 spelling: javascript
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:24:29 -05:00
Josh Soref 8834253502 spelling: into
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:24:29 -05:00
Josh Soref 7feea370ed spelling: highlight
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:24:29 -05:00
Josh Soref 680f0d1e20 spelling: github
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:24:29 -05:00
Josh Soref bc53300e33 spelling: dynamic
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:24:29 -05:00
Josh Soref af0927cf5c spelling: consequences
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:24:29 -05:00
dependabot[bot] ee665c0c68
Bump github.com/writeas/web-core
Bumps [github.com/writeas/web-core](https://github.com/writeas/web-core) from 1.4.1-0.20220118212728-0da0bcaf018e to 1.4.1.
- [Release notes](https://github.com/writeas/web-core/releases)
- [Commits](https://github.com/writeas/web-core/commits/v1.4.1)

---
updated-dependencies:
- dependency-name: github.com/writeas/web-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-01 23:00:16 +00:00
Abdullah 83765d5cbc
Update Makefile 2023-01-31 21:27:56 +00:00
İlteriş Yağıztegin Eroğlu 77cc1cc816 fix Makefile and Dockerfile to build on latest go versions
Signed-off-by: İlteriş Yağıztegin Eroğlu <ilteris.eroglu@trendyol.com>
2023-01-20 11:36:11 +00:00
Matt Baer 118eb732f4 Merge branch 'develop' into letters 2023-01-08 11:49:57 -05:00
Matt Baer 99d72881cf Catch and output directory walking errors
Previously, app would panic and admins would see unhelpful errors.

This closes #620
2023-01-06 15:34:22 -05:00
Timshel fc5a79a6bc Use TEXT for oauth_users.access_token to prevent insertion failure 2023-01-03 19:32:21 +01:00
Matt Baer a0f1e1821f Delete socket file on server shutdown 2022-12-26 13:20:28 -05:00
Matt Baer f84b4b0f74 Use more idiomatic variable initialization for network/protocol 2022-12-26 13:18:45 -05:00
Matt Baer 7a84d27dca Re-use err variable, instead of creating new error vars 2022-12-26 13:17:56 -05:00
Matt Baer 3e6669828c
Merge pull request #605 from writefreely/dependabot/go_modules/github.com/go-ini/ini-1.67.0
Bump github.com/go-ini/ini from 1.66.4 to 1.67.0
2022-12-25 00:51:59 -05:00
Matt Baer bbcb61bc53
Merge pull request #531 from writefreely/markdown-descriptions
Support Markdown and rel=me links in blog descriptions
2022-12-25 00:48:46 -05:00
Matt Baer 8684ff04a4 Merge branch 'develop' into markdown-descriptions 2022-12-25 00:47:41 -05:00
Matt Baer 93d5fd152d
Merge pull request #610 from lpar/remove-go-bindata
Replace go-bindata with standard go:embed
2022-12-25 00:24:36 -05:00
mathew 6903dd4349 Replace go-bindata with standard go:embed 2022-12-24 17:59:19 -06:00
dependabot[bot] b5021f2b0c
Bump github.com/go-ini/ini from 1.66.4 to 1.67.0
Bumps [github.com/go-ini/ini](https://github.com/go-ini/ini) from 1.66.4 to 1.67.0.
- [Release notes](https://github.com/go-ini/ini/releases)
- [Commits](https://github.com/go-ini/ini/compare/v1.66.4...v1.67.0)

---
updated-dependencies:
- dependency-name: github.com/go-ini/ini
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-01 22:02:03 +00:00
Matt Baer 29c898867a
Merge pull request #483 from writefreely/log-gone-user-out
Log user out when authenticated as deleted user
2022-11-14 23:45:33 -05:00
Matt Baer 17614b5e02
Merge pull request #533 from writefreely/custom-css
Support custom instance-level stylesheets with file
2022-11-14 23:43:32 -05:00
Matt Baer 950090c0d7 Fix whitespace in templates/user/include/header.tmpl 2022-11-14 23:42:37 -05:00
Matt Baer 01c920b253 Merge branch 'develop' into custom-css 2022-11-14 23:41:36 -05:00
Matt Baer 4c1678f91e
Merge pull request #537 from dariusk/fix-drop
Prevent dropping of external files onto editor
2022-11-14 23:38:38 -05:00
Matt Baer 4b33c51ece
Merge pull request #540 from writefreely/better-titles-stats
Show post excerpt in stats list when no post title
2022-11-14 23:37:21 -05:00
Matt Baer 99d17e5e97
Merge pull request #596 from writefreely/dependabot/go_modules/github.com/urfave/cli/v2-2.23.5
Bump github.com/urfave/cli/v2 from 2.5.1 to 2.23.5
2022-11-11 03:41:25 -05:00
dependabot[bot] 6347301867
Bump github.com/urfave/cli/v2 from 2.5.1 to 2.23.5
Bumps [github.com/urfave/cli/v2](https://github.com/urfave/cli) from 2.5.1 to 2.23.5.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v2.5.1...v2.23.5)

---
updated-dependencies:
- dependency-name: github.com/urfave/cli/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-11 08:36:55 +00:00
Matt Baer 7f83bb2706
Merge pull request #594 from writefreely/dependabot/go_modules/github.com/mattn/go-sqlite3-1.14.16
Bump github.com/mattn/go-sqlite3 from 1.14.6 to 1.14.16
2022-11-11 03:36:14 -05:00
Matt Baer 02383768ed
Merge pull request #591 from writefreely/dependabot/go_modules/github.com/stretchr/testify-1.8.1
Bump github.com/stretchr/testify from 1.7.0 to 1.8.1
2022-11-11 03:35:43 -05:00
Matt Baer f85241e037
Merge pull request #589 from writefreely/dependabot/github_actions/docker/metadata-action-4.1.1
Bump docker/metadata-action from 4.0.1 to 4.1.1
2022-11-11 03:35:05 -05:00
dependabot[bot] a080e51aaa
Bump github.com/mattn/go-sqlite3 from 1.14.6 to 1.14.16
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.6 to 1.14.16.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.6...v1.14.16)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-sqlite3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-11 08:34:57 +00:00
Matt Baer 57b12f31c9
Merge pull request #587 from writefreely/dependabot/github_actions/docker/login-action-2.1.0
Bump docker/login-action from 2.0.0 to 2.1.0
2022-11-11 03:34:36 -05:00
Matt Baer c58eedba7d
Merge pull request #586 from writefreely/dependabot/github_actions/docker/build-push-action-3.2.0
Bump docker/build-push-action from 3.0.0 to 3.2.0
2022-11-11 03:34:15 -05:00
Matt Baer 9767910b1f
Merge pull request #560 from writefreely/dependabot/go_modules/github.com/gorilla/csrf-1.7.1
Bump github.com/gorilla/csrf from 1.7.0 to 1.7.1
2022-11-11 03:33:46 -05:00
Matt Baer ac1b947b18
Merge pull request #563 from writefreely/dependabot/go_modules/github.com/fatih/color-1.13.0
Bump github.com/fatih/color from 1.10.0 to 1.13.0
2022-11-11 03:33:09 -05:00
dependabot[bot] a5c80b98e7
Bump github.com/guregu/null
Bumps [github.com/guregu/null](https://github.com/guregu/null) from 3.5.0+incompatible to 4.0.0+incompatible.
- [Release notes](https://github.com/guregu/null/releases)
- [Commits](https://github.com/guregu/null/compare/v3.5.0...v4.0.0)

---
updated-dependencies:
- dependency-name: github.com/guregu/null
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-11 08:29:20 +00:00
dependabot[bot] 7b5326ada9
Bump github.com/fatih/color from 1.10.0 to 1.13.0
Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.10.0 to 1.13.0.
- [Release notes](https://github.com/fatih/color/releases)
- [Commits](https://github.com/fatih/color/compare/v1.10.0...v1.13.0)

---
updated-dependencies:
- dependency-name: github.com/fatih/color
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-11 08:29:06 +00:00
Matt Baer 2c644dd262
Merge pull request #562 from writefreely/dependabot/go_modules/github.com/gorilla/sessions-1.2.1
Bump github.com/gorilla/sessions from 1.2.0 to 1.2.1
2022-11-11 03:28:28 -05:00
Matt Baer 7687341512
Merge pull request #554 from writefreely/dependabot/go_modules/github.com/manifoldco/promptui-0.9.0
Bump github.com/manifoldco/promptui from 0.8.0 to 0.9.0
2022-11-11 03:27:19 -05:00
dependabot[bot] beb964a9f1
Bump github.com/stretchr/testify from 1.7.0 to 1.8.1
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.0 to 1.8.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-11 07:58:29 +00:00
dependabot[bot] 42c7e22b98
Bump github.com/gorilla/csrf from 1.7.0 to 1.7.1
Bumps [github.com/gorilla/csrf](https://github.com/gorilla/csrf) from 1.7.0 to 1.7.1.
- [Release notes](https://github.com/gorilla/csrf/releases)
- [Commits](https://github.com/gorilla/csrf/compare/v1.7.0...v1.7.1)

---
updated-dependencies:
- dependency-name: github.com/gorilla/csrf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-11 07:58:27 +00:00
dependabot[bot] 4f2b17ddb1
Bump github.com/manifoldco/promptui from 0.8.0 to 0.9.0
Bumps [github.com/manifoldco/promptui](https://github.com/manifoldco/promptui) from 0.8.0 to 0.9.0.
- [Release notes](https://github.com/manifoldco/promptui/releases)
- [Changelog](https://github.com/manifoldco/promptui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/manifoldco/promptui/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: github.com/manifoldco/promptui
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-11 07:58:27 +00:00
Matt Baer 63eb682a60 Update package-lock.json 2022-11-11 02:13:10 -05:00
Matt Baer ccef3bfdc7 Update go.mod to Go 1.19 and tidy
From Go 1.15
2022-11-11 02:12:54 -05:00
Matt Baer 2c44fb780a Upgrade web-core package
This fixes PEM key decoding issues, and federation
2022-11-11 02:11:55 -05:00
Matt Baer f43a3a8bfa
Merge pull request #593 from writefreely/dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.21
Bump github.com/microcosm-cc/bluemonday from 1.0.5 to 1.0.21
2022-11-10 23:56:19 -05:00
Matt Baer 61d1537fce
Merge branch 'hotfix-0.13.2' into dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.21 2022-11-10 23:56:04 -05:00
Matt Baer d08f067e9c Change copyright notices to Musing Studio LLC
A Bunch Tell is now Musing Studio.
2022-11-10 23:49:16 -05:00
dependabot[bot] 3696483d91
Bump github.com/microcosm-cc/bluemonday from 1.0.5 to 1.0.21
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.5 to 1.0.21.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.5...v1.0.21)

---
updated-dependencies:
- dependency-name: github.com/microcosm-cc/bluemonday
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-01 22:07:53 +00:00
dependabot[bot] 11266dd87e
Bump docker/metadata-action from 4.0.1 to 4.1.1
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4.0.1 to 4.1.1.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v4.0.1...v4.1.1)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-17 22:39:21 +00:00
dependabot[bot] de0c1085b4
Bump docker/login-action from 2.0.0 to 2.1.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2.0.0...v2.1.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-12 22:39:28 +00:00
dependabot[bot] 2cf7693a8e
Bump docker/build-push-action from 3.0.0 to 3.2.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3.0.0 to 3.2.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3.0.0...v3.2.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-12 22:39:26 +00:00
Matt Baer b74fd70ab5 Bump version to v0.13.2 2022-07-22 00:22:26 -04:00
dependabot[bot] 915351c4af
Bump github.com/gorilla/sessions from 1.2.0 to 1.2.1
Bumps [github.com/gorilla/sessions](https://github.com/gorilla/sessions) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/gorilla/sessions/releases)
- [Commits](https://github.com/gorilla/sessions/compare/v1.2.0...v1.2.1)

---
updated-dependencies:
- dependency-name: github.com/gorilla/sessions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-31 12:40:50 +00:00
Matt Baer 92504b6721
Merge pull request #553 from davralin/create-container-from-github-action
Create container from GitHub action
2022-05-31 08:39:36 -04:00
davralin 16c6788b62
Switch target branch 2022-05-27 14:01:54 +02:00
davralin 2433745504
Merge pull request #15 from davralin/develop
Update GH-actions
2022-05-25 19:47:18 +02:00
davralin 17c8e78a5c
Update GH-actions 2022-05-25 19:43:10 +02:00
davralin 84fea7abba
Remove unneeded file 2022-05-25 19:12:14 +02:00
davralin 59767804a9
Revert build 2022-05-25 18:45:59 +02:00
davralin dec0142a5b
Update Dockerfile 2022-05-25 18:43:13 +02:00
davralin f2dce539f4
Adjust default 2022-05-25 18:37:40 +02:00
davralin ea0703949d
Create docker-publish.yml 2022-05-25 18:30:26 +02:00
davralin 35ac24223d
Add stuff 2022-05-25 18:29:06 +02:00
ltdk 0a19dc1ec2 Add editorconfig 2022-05-11 13:11:22 -04:00
ltdk baaf0580f5 Add unix socket support
Enables listening on unix sockets by specifying a file path for the bind address
2022-05-10 23:51:13 -04:00
Matt Baer e5103d555f Use ini pkg on GitHub
This fixes "unknown revision v1.66.4" error
2022-04-29 14:28:55 -04:00
Matt Baer cab7fc8647 Update ini pkg to v1.66.4 2022-04-29 14:25:56 -04:00
Matt Baer face603a0e Move to go-gopher library on GitHub 2022-04-29 14:21:22 -04:00
Matt Baer 9a45030911 Change to code.as/wf/go-gopher library
This fixes build issues we're having due to the SSL certificate
on git.mills.io. It also updates to the latest version of the
go-gopher library.
2022-04-29 13:13:25 -04:00
Matt Baer 4680e2e046 Show post excerpt in stats list when no post title
Originally requested on the forum:
https://discuss.write.as/t/use-non-random-ids-for-untitled-posts-in-blog-stats/5046
2022-03-29 13:23:20 -04:00
Matt Baer c3ae4e6d3c Remove blog name in newsletter email subject
Originally requested on the forum:
https://discuss.write.as/t/minimize-subject-of-email-updates/3881
2022-03-29 13:00:53 -04:00
Darius Kazemi dd88083b2a Prevent dropping of external files onto editor
We now detect whether a file is being dropped by a user (by checking `event.dataTransfer.files.length`) and prevent the remaining drop behavior if this is the case. Otherwise, drop happens like normal (so a user can still drop text into the editor, or even an image that has been uploaded already via normal means and rendered in the editor).
2022-03-03 10:25:47 -08:00
Matt Baer fd44bc5707 Fix whitespace 2022-01-31 22:24:56 -05:00
Matt Baer 9ee83ae885 Support custom instance-level stylesheet
Will render if a file exists at: static/local/custom.css

Ref T563
2022-01-31 22:18:52 -05:00
Matt Baer e92c33aae4
Merge pull request #515 from gytisrepecka/fix-gitea-oauth-ext-user-id
#514 - Fix login with Gitea OAuth: external user ID not recorded
2022-01-28 08:59:07 -05:00
Matt Baer 0d554ce180
Merge pull request #525 from writefreely/fix-monetization-sqlite-query
Fix monetization_pointer upsert query for SQLite
2022-01-18 16:47:47 -05:00
Matt Baer a0e936ee1b Support Markdown and rel=me links in blog descriptions
Ref T874 T744
2022-01-18 16:43:17 -05:00
Matt Baer 46bb8e65a1 Add security policy
Closes #522
2022-01-11 09:41:25 -05:00
Isaac Su df7be46417 Protect drafts if they are part of a Private or Protected collection 2022-01-11 16:31:11 +11:00
Matt Baer d1e6daee16 Fix monetization_pointer upsert query for SQLite
Fixes #495
2021-12-29 18:23:31 -05:00
Matt Baer 43ca80f3eb
Merge pull request #503 from mnlg/hotfix/fix-date-format
Fix date format in anonymous posts
2021-12-29 18:05:36 -05:00
Matt Baer 1530bf37ef
Merge pull request #506 from writefreely/minor-fixes
Miscellaneous fixes
2021-12-29 17:53:16 -05:00
Matt Baer 401c8c1f4c
Merge pull request #520 from writefreely/slug-improvements
Elide Markdown when generating slug
2021-12-29 17:51:42 -05:00
Eli Mellen b190a1508b
Merge pull request #1 from eli-oat/eli-oat-remove-cheeky-404-message
Update 404-general.tmpl
2021-12-25 14:05:05 -05:00
Eli Mellen 27f68ef0cf
Update 404-general.tmpl
Remove gaslighting error message and replace with one that is a wee bit more respectful to the human using the software.
2021-12-25 14:03:40 -05:00
Matt Baer 69ab0d34e0 Elide Markdown when generating slug
This makes sure e.g. link URLs and image alt text don't get included
in the slug.

Ref T329
2021-12-21 13:55:10 -05:00
gytisrepecka 97a5121924
#514 - Fix login with Gitea OAuth problems: external user ID not properly recorded. 2021-11-28 13:38:30 +02:00
Matt Baer 129f428bfa Remove unnecessary "upgrade" collection template 2021-11-24 12:45:02 -05:00
Matt Baer 8c1785b904
Merge pull request #505 from lt3Dev/pr/relative-urls
Templates: use relative URLs for static assets
2021-11-24 11:39:06 -06:00
Matt Baer a2f9642238 Use underscores for em text in Rich editor
This fixes an issue where mixing strong and em text caused the text
to not render correctly as a post (e.g. *This is **bold text**.*
would fail).
2021-10-22 11:45:37 -05:00
Matt Baer 5b3d25b5cc Don't use dot import for github.com/gorilla/feeds 2021-09-29 12:48:50 -04:00
Matt Baer 6e5f7e87d2
Merge pull request #501 from mnlg/develop
Fix Gopher collections query
2021-09-29 11:38:13 -05:00
Matt Baer e91748c0bc Return correct count of currently-published lang posts
Previously, we'd include scheduled posts, too.

Ref T805
2021-09-16 15:53:07 -04:00
Matt Baer 414d5b0a1c Add pagination routes on lang post filter
Ref T805
2021-09-16 14:23:35 -04:00
Matt Baer c4b124e37c Limit lang filter to 2 characters
Ref T805
2021-09-16 14:05:52 -04:00
Matt Baer f4977c7a34 Support filtering blog posts by language
Ref T805
2021-09-13 18:36:36 -04:00
Matt Baer 6ad1f41cf4 Prevent crash on empty title
This title, typed directly in the plain editor, would previously crash
the app / show the user a "server error":

# #
2021-08-31 11:18:58 -04:00
Matt Baer 3270470b68 Ignore post `created` date when empty on publish 2021-08-30 17:48:30 -04:00
Matt Baer 2a0298cd46 Make metadata dropdown design consistent 2021-08-30 17:46:02 -04:00
Matt Baer a122e4e98a
Merge pull request #502 from egon0/develop
fix Dockerfile, nodejs-npm was renamed in alpine to npm
2021-08-23 13:08:32 -05:00
Matt Baer 44bfd4573e Support keyboard navigation on pad publish target dropdown 2021-08-23 14:07:25 -04:00
HeartDev cc69f9f2f1 Templates: use relative URLs for static assets
I noticed most asset links use relative URLs, except for a few. This commit remedies this inconsistency.
OpenGraph embeds were not changed, because in my experience, some embed scrapers require absolute URLs.

Motivation: The site I use has an onion and a clearnet version, and currently, visiting the onion loads fonts, styles, and the favicon
over the clearnet.
2021-08-21 10:18:27 +00:00
mnlg ae7e42e24e Fix date format in anonymous posts 2021-08-13 18:43:17 +02:00
Matt Baer fc8e209def Strip Markdown from Letter subjects
Ref T856
2021-08-10 18:05:24 -04:00
Matt Baer e963755393 Set 'To' addresses on Letter email after message is prepared
This works with mailgun.AddRecipientAndVariables, so we can safely send
emails to a large number of recipients beyond Mailgun's 1,000-recipient
limit.

Ref T856
2021-08-10 18:01:19 -04:00
Matt Baer 2288ccf2a2 Merge branch 'develop' into letters 2021-08-10 17:47:23 -04:00
Micha Gläß-Stöcker a58180543e fix Dockerfile, nodejs-npm was renamed in alpine to npm 2021-08-08 22:27:33 +02:00
mnlg 5be1938a8a Fix Gopher collections query 2021-08-05 16:53:20 +02:00
Matt Baer c42439886c
Merge pull request #497 from writefreely/fix-go-gopher-import
Update go-gopher import path to new location
2021-07-23 13:30:14 -04:00
Matt Baer adb4fdc5fe Update go-gopher import path to new location
Fixes #496
2021-07-21 17:28:48 -04:00
Matt Baer b7f732b915
Merge pull request #485 from writefreely/hotfix-0.13.1
0.13.1 hotfix
2021-06-30 13:17:02 -04:00
Matt Baer 940d220bf3 Bump version to 0.13.1 2021-06-30 10:33:08 -04:00
Matt Baer 48075fc183
Merge pull request #474 from writefreely/fix-gen-oauth-empty-user-id
Error when generic OAuth provider doesn't return a user ID
2021-06-30 10:12:50 -04:00
Matt Baer 577bdf14aa
Merge pull request #484 from writefreely/fix-classic-backslashes
Fix trailing backslash on lists in Classic editor
2021-06-30 09:58:02 -04:00
Matt Baer 672fa10b94
Merge pull request #477 from writefreely/fix-sqlite-reader-lock
Fix SQLite database lock on Reader
2021-06-30 09:56:16 -04:00
Matt Baer de5e91cb71 Fix trailing backslash on lists in Classic editor
Previously, when editing a post with an unordered list in it via the
Classic editor, backslashes (\) would get added to the end of each list
item. This fixes that.

Closes #480
2021-06-29 13:07:10 -04:00
Matt Baer 6291f4f155
Merge pull request #479 from writefreely/fix-stats-no-host-log
Fix "Collection.hostName is empty!" log on Stats page
2021-06-29 12:46:47 -04:00
Matt Baer 273c9cf418
Merge pull request #471 from writefreely/fix-title-lists
Don't render title as list item
2021-06-29 10:52:39 -04:00
Matt Baer fbb3000e4d
Merge pull request #476 from writefreely/strip-image-alt-meta
Don't include img alt text in post Summary()
2021-06-28 15:47:42 -04:00
Matt Baer 6b336e22aa Log user out when authenticated as deleted user
Now when we check for the user at certain times and find that the user
doesn't exist in the database, we log them out and send them back to
the home page.
2021-06-27 17:57:07 -04:00
Matt Baer cbc2427475 Don't apply "nofollow" to links on single-user instances 2021-06-27 10:51:53 -04:00
Matt Baer 276304d5b8 Rearrange applyMarkdownSpecial parameters 2021-06-27 10:35:36 -04:00
Matt Baer 65bc73e527
Merge pull request #475 from writefreely/fix-feed-redirect
Remove additional slash in /feed/ redirect
2021-06-26 09:37:18 -04:00
Matt Baer d37ab544e8 Prevent out of bounds error on title with only whitespace 2021-06-25 17:08:59 -04:00
Matt Baer 1bdcf7096a Fix "Collection.hostName is empty!" log on Stats page
Fixes #468
2021-06-25 12:39:59 -04:00
Matt Baer ed771380fb Fix SQLite database lock on Reader
Fetching posts for the Reader involves an additional query on each row,
which previously ran into our connection limit to the database and caused
it to lock up. This increases the connection limit from 1 to 2, to allow
this.

This is meant to be a quick, safe fix, but there could always be a better
solution.

Fixes #467
2021-06-25 12:10:19 -04:00
Matt Baer 720a8c1975 Don't include img alt text in post Summary()
Fixes #306
2021-06-25 11:16:03 -04:00
Matt Baer f933b36170 Prevent out of bounds error when post has no title 2021-06-23 17:38:22 -04:00
Matt Baer e91ffe2dcb Remove additional slash in /feed/ redirect 2021-06-23 16:50:23 -04:00
Matt Baer 3008668a7d Error when generic OAuth provider doesn't return a user ID
...on the OAuth access token inspection call. This returns an error and
privately (via logs) prompts the admin to add a `map_user_id` config value.

Fixes #469
2021-06-23 12:43:57 -04:00
Matt Baer 0ddca40529 Don't render title as list item
This fixes an issue where "12. April" would get rendered as "1. April" because
it looks like a Markdown list item to our renderer. Now, we parse titles as
titles, instead of standalone text, which causes the renderer to give us the
results we want. This also adds some basic tests for the applyBasicMarkdown()
func.

Closes #470
2021-06-22 16:06:04 -04:00
Matt Baer 2ea235f0c4 Support email subscriptions (base)
This adds beginning email subscription functionality, with only MySQL support,
Mailgun support, and incomplete support for private instances. It includes
database changes, so run:

    writefreely db migrate

to use this feature.

Ref T856
2021-06-21 18:24:40 -04:00
Matt Baer e983c4527f Update README links and intro text 2021-06-11 12:04:13 -04:00
Matt Baer 25e4d6448b Fix ineffectual assignments in migrations 2021-06-09 14:25:55 -04:00
Matt Baer 230c736583 Run gofmt on files that need it 2021-06-09 14:22:13 -04:00
Matt Baer e7245536f3
Merge pull request #463 from writefreely/wm-fix
Web Monetization fixes + exclusive content
2021-06-09 14:03:24 -04:00
Matt Baer 42db4b38f6 Truncate paid posts and show badge on Reader 2021-06-09 11:09:53 -04:00
Matt Baer c05f7056c4 Fix collection rendering in Chorus mode 2021-06-09 10:04:28 -04:00
Matt Baer e42ba392c6 Support Web Monetized split content
Ref T770
2021-06-07 15:52:24 -04:00
Matt Baer 9341784c0c Fix OAuth signup with collection description 2021-06-07 15:09:12 -04:00
Matt Baer f0697fd555 Merge branch 'develop' into wm-fix 2021-06-07 14:58:14 -04:00
Matt Baer 7695f8c2e4
Merge pull request #464 from writefreely/api-updates
Final API changes
2021-06-07 14:56:45 -04:00
Matt Baer 85fb2a952b Support setting `description` on user registration 2021-06-07 14:53:22 -04:00
Matt Baer 6740fbe097 Support publishing title-only posts 2021-05-25 17:04:17 -04:00
Matt Baer 2938bba15a Support updating collection only with monetization_pointer 2021-05-25 16:58:09 -04:00
Matt Baer ddc7087d1e Fix Web Monetization option not showing on Customize page 2021-05-25 10:17:57 -04:00
Matt Baer b010484493 Return `url` of new post on publish 2021-05-20 20:44:59 -04:00
Matt Baer 73e0b72878 Fix release build
This forces xgo to use Go 1.15, to work around a bug with Go modules:
https://github.com/techknowlogick/xgo/issues/109#issuecomment-835494720

This also uses the correct Darwin and Windows binary names to prevent
failures in the `make release` process.
2021-05-10 13:08:49 -04:00
Matt Baer 14f5100d6a Bump version to 0.13.0 2021-05-08 15:31:32 -04:00
Matt Baer 5c89812764
Merge pull request #460 from writefreely/add-funding-info
Enable GitHub Sponsor button
2021-05-08 12:27:39 -04:00
Matt Baer 7a71731274 Add GitHub Sponsors profile 2021-05-08 12:27:05 -04:00
Matt Baer b0f792c211 Add Open Collective funding profile 2021-05-07 12:44:50 -04:00
Matt Baer 73450a50e3
Merge pull request #356 from writefreely/draft-list-paging
Draft list paging
2021-05-04 09:39:22 -04:00
Matt Baer 895e04c8c4
Merge pull request #443 from writefreely/minor-fixes
Minor WYSIWYG and miscellaneous fixes
2021-04-30 11:31:06 -04:00
Matt Baer 4565c6dd90 Only use SameSite=None on Secure site
This fixes logging in when developing on newer versions of Chrome.
2021-04-30 11:03:42 -04:00
Matt Baer a7c4a318f3
Merge pull request #458 from writefreely/improve-gopher
Fix Gopher links and add blog info
2021-04-30 10:58:34 -04:00
Matt Baer 7c32dc1045
Merge pull request #457 from writefreely/intl-domain-support
Support international domain names
2021-04-30 09:45:42 -04:00
Matt Baer 2903c86875 Remove prose.bundle.js from repo
This file is compiled from other sources, and thus shouldn't be
included in the repo.
2021-04-30 09:41:18 -04:00
Matt Baer e5347dd924 Move Classic editor horizontal spacing inside .ProseMirror 2021-04-30 09:38:28 -04:00
Matt Baer c9c2adde0f Use Sans font for Classic editor menubar 2021-04-30 09:25:24 -04:00
Matt Baer b2c6c6c167 Rename wysiwyg editor to classic 2021-04-29 17:09:24 -04:00
Matt Baer 5a4ff2a9de Support blockquotes in Classic editor
Ref T727
2021-04-29 17:07:40 -04:00
Matt Baer c01fb585ba Support horizontal rules in Classic editor
Ref T727
2021-04-29 17:05:18 -04:00
Matt Baer affcd270bb
Merge pull request #204 from writefreely/T319-user-delete-acct
T319 user delete acct
2021-04-28 17:57:34 -04:00
Matt Baer 14a8961457 Show correct extracted title on loaded posts page
Ref T401
2021-04-28 17:19:22 -04:00
Matt Baer 4e0912b32a
Merge pull request #203 from writefreely/T319-admin-delete-acct
T319 admin delete acct
2021-04-28 09:46:56 -04:00
Matt Baer 02bb5013a7 Show blog title and description via Gopher 2021-04-27 11:39:28 -04:00
Matt Baer 7257af2905 Strip HTTP port from Gopher links
Previously, if running an instance on e.g. http://localhost:8080, the port
would show up in the Gopher links and potentially cause rendering to fail.
This fixes that.
2021-04-27 11:30:04 -04:00
Matt Baer 36455eea2b Remove debug log 2021-04-26 11:54:42 -04:00
Matt Baer 967ee9679c Support international domain names
This internally converts the configured host name into its Punycode ASCII
representation, while showing users the correct Unicode domain name.
2021-04-26 11:18:51 -04:00
Matt Baer d3d77cee54 Make open account deletion configurable
This adds a configuration option to the [app] section: open_deletion. When
true, users can delete their account on their own.

Ref T319
2021-04-22 13:13:47 -04:00
Matt Baer 7c1c1218b1 Tweak "deletion success" message and note it doesn't work
Ref T319
2021-04-22 12:45:55 -04:00
Matt Baer b092421f6e Add Cross-Site Request Forgery (CSRF) protection on account deletion
This requires admins to generate a new encryption key with:
  writefreely keys generate

Ref T319
2021-04-22 12:41:54 -04:00
Matt Baer a6c93c37da Move user account deletion to confirmation modal
This mimics the admin UI for deleting a user account.

Ref T319
2021-04-22 11:55:17 -04:00
Matt Baer 1d8facfe1c Prevent admin self-deletion in API
Ref T319
2021-04-22 11:37:02 -04:00
Matt Baer f689706baa Merge branch 'T319-admin-delete-acct' into T319-user-delete-acct 2021-04-22 10:14:48 -04:00
Matt Baer f06ab629d1 Make user deletion success message more concise
Ref T319
2021-04-22 10:11:18 -04:00
Matt Baer e4164cbf67 Move admin user deletion confirmation to modal
Ref T319
2021-04-22 10:08:05 -04:00
Matt Baer 3b58d77e67 Merge branch 'develop' into T319-admin-delete-acct 2021-04-22 09:07:40 -04:00
Matt Baer c0fdd8af49
Merge pull request #452 from writefreely/fix-ace-touchscreens
Disable Ace editor on touchscreen devices
2021-04-19 16:25:53 -04:00
Matt Baer c06a739f9b
Merge pull request #450 from writefreely/fix-markdown-import-safari
Accept general text/* files on post import
2021-04-19 16:23:45 -04:00
Matt Baer 4ec8ffa699
Merge pull request #449 from writefreely/fix-actions-on-mobile
Always show post actions on mobile devices
2021-04-19 16:22:44 -04:00
Matt Baer e0a0d71c84 Revert "Use Sans font for post action buttons"
This reverts commit af4e0b4f1c.
2021-04-19 16:22:01 -04:00
Matt Baer 3ab21f7834
Merge pull request #445 from writefreely/remove-nerds-dep
Remove writeas/nerds/store pkg dependency
2021-04-19 16:19:52 -04:00
Matt Baer 61974fadc0 Merge branch 'develop' into remove-nerds-dep 2021-04-19 16:18:33 -04:00
Matt Baer 439f8bd262
Merge pull request #444 from writefreely/log-out-pass-blog
Support logging out of password-protected blogs
2021-04-12 14:11:12 -04:00
Matt Baer 63fa8d299a Include 'move to...' action in loaded draft posts
Ref T401
2021-04-07 16:44:18 -04:00
Matt Baer 27b43ac2f1 Merge branch 'develop' into draft-list-paging 2021-04-07 15:58:25 -04:00
Matt Baer 51a83069c4 Disable Ace editor on touchscreen devices
Ace doesn't work well with touchscreen devices, so instead we fall back to a
plain textarea.
2021-04-07 14:58:09 -04:00
Matt Baer ac7583eadb
Merge pull request #384 from colin-axner/374-fix-silenced-post-accessibility
fix accessibility of silenced user posts
2021-04-07 14:01:04 -04:00
Colin Axnér 8ac2d0b310 merge develop 2021-04-07 18:33:02 +02:00
Colin Axnér 866a585119 fix compile error 2021-04-07 18:31:45 +02:00
Matt Baer 4228761eb3
Merge pull request #431 from Dak425/stop-federating-protected-and-private-blogs
Stop private and protected blogs from federating
2021-04-07 10:54:28 -04:00
Donald Feury 68297acb74 Moved guard clauses to stop federation before debug logging 2021-04-07 11:27:25 -04:00
Matt Baer de601e16ac
Merge pull request #451 from writefreely/move-repo
Update repo URL to writefreely org
2021-04-06 17:27:17 -04:00
Matt Baer 484d2736ce Update repo URL to writefreely org
From the writeas org on GitHub.
2021-04-06 17:24:07 -04:00
Matt Baer f8888df746 Accept general text/* files on post import
This fixes an issue with Safari not allowing users to select *.md files.

Closes #334
2021-04-06 15:59:32 -04:00
Matt Baer 0c7aba1f53
Merge pull request #442 from writeas/add-attachments
Include images as attachments in ActivityStreams data
2021-04-06 12:44:30 -04:00
Matt Baer 02490c798c Only set lang on initial publish in all editors
This fixes #280 in all editors, continuing work from #435.
2021-04-06 12:38:45 -04:00
Matt Baer 11e636359d Clean up lang field setting in pad.tmpl
This continues work by @cjeller1592 in #435.
2021-04-06 12:37:26 -04:00
Matt Baer 50c4e944a4 Merge branch 'develop' into minor-fixes 2021-04-06 12:30:37 -04:00
Matt Baer e58e457b25
Merge pull request #435 from cjeller1592/lang-metadata
Add conditional for preserving lang metadata
2021-04-06 12:17:35 -04:00
Matt Baer af4e0b4f1c Use Sans font for post action buttons 2021-04-06 12:11:45 -04:00
Matt Baer ed74228795
Merge pull request #434 from cjeller1592/compose-update-wf-image
Update writefreely-web image in docker-compose.yml
2021-04-06 11:59:26 -04:00
Matt Baer 2c1d3a51af
Merge pull request #429 from x4e/develop
Case insensitive language highlighting matching
2021-04-06 11:31:08 -04:00
Matt Baer 23818c6104
Merge pull request #426 from mostfunkyduck/develop
Fixes problem where all links were breaking in gopher
2021-04-06 11:26:46 -04:00
Matt Baer 5510ef15b5 Always show post actions on mobile devices
This maintains previous show-on-hover effect for post actions on blog
index, but always shows the actions when on a mobile device.

Fixes #309
2021-04-06 11:09:38 -04:00
Matt Baer 5ecf613cb5
Merge pull request #448 from writeas/dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.5
Bump github.com/microcosm-cc/bluemonday from 1.0.4 to 1.0.5
2021-04-01 07:26:05 -04:00
dependabot[bot] 9cbd254d64
Bump github.com/microcosm-cc/bluemonday from 1.0.4 to 1.0.5
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.4...v1.0.5)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-01 11:24:37 +00:00
Matt Baer 733301d364
Merge pull request #447 from writeas/dependabot/go_modules/github.com/go-sql-driver/mysql-1.6.0
Bump github.com/go-sql-driver/mysql from 1.5.0 to 1.6.0
2021-04-01 07:24:26 -04:00
Matt Baer f1eae4007e
Merge pull request #446 from writeas/dependabot/go_modules/github.com/hashicorp/go-multierror-1.1.1
Bump github.com/hashicorp/go-multierror from 1.1.0 to 1.1.1
2021-04-01 07:22:09 -04:00
dependabot[bot] f70fc0c4e2
Bump github.com/go-sql-driver/mysql from 1.5.0 to 1.6.0
Bumps [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/go-sql-driver/mysql/releases)
- [Changelog](https://github.com/go-sql-driver/mysql/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-sql-driver/mysql/compare/v1.5.0...v1.6.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-01 05:14:03 +00:00
dependabot[bot] 2a9aa84366
Bump github.com/hashicorp/go-multierror from 1.1.0 to 1.1.1
Bumps [github.com/hashicorp/go-multierror](https://github.com/hashicorp/go-multierror) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/hashicorp/go-multierror/releases)
- [Commits](https://github.com/hashicorp/go-multierror/compare/v1.1.0...v1.1.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-01 05:13:09 +00:00
Matt Baer 64f1d71524 Remove writeas/nerds/store dependency 2021-03-30 12:49:12 -04:00
Matt Baer 5a3e8d59b6 Support logging out of password-protected blogs
Closes T492
2021-03-29 20:55:21 -04:00
Matt Baer 6f665e7e4b Fix light / dark mode toggle in WYSIWYG editor 2021-03-25 13:02:57 -04:00
Matt Baer d7c9f56b40 Rename ProseMirror writeAs Parser/Serializer to writeFreely 2021-03-25 13:02:57 -04:00
Matt Baer 47aa436caa Render `tight` Markdown lists in WYSIWYG editor 2021-03-25 13:02:57 -04:00
Matt Baer 424bd55816 Set SameSite=None on session cookie 2021-03-25 13:02:57 -04:00
Matt Baer 3e282e4c85 Rename MonetizationPointer field to Monetization 2021-03-25 13:02:35 -04:00
Matt Baer 85efbcccfc
Merge pull request #439 from writeas/support-authorized-fetch
Fix following from Mastodon with Authorized Fetch enabled
2021-03-25 08:46:23 -04:00
Matt Baer 4a58a94e26 Include images as attachments in ActivityStreams data
Ref T709
2021-03-24 16:00:52 -04:00
Matt Baer 9aa5fc4420 Fix ProseMirror failing to parse Markdown images 2021-03-19 17:02:00 -04:00
Matt Baer 636c9b35c0 Use WriteFreely ProseMirror packages 2021-03-19 17:00:21 -04:00
Matt Baer a6a4bd38c1 Fix overflowing WYSIWYG editor images 2021-03-19 16:59:01 -04:00
Matt Baer 811f996e84
Merge pull request #437 from writeas/post-signature-pinned-posts
Post signature improvements
2021-03-19 13:28:54 -04:00
Matt Baer 3984042905
Merge pull request #405 from dariusk/dariusk/inspectuser
Generic OAuth userinfo properies now configurable
2021-03-18 19:28:43 -04:00
Matt Baer 321c1af607
Merge pull request #436 from writeas/publish-with-slug
Support `slug` parameter when publishing a post
2021-03-09 09:59:57 -05:00
Matt Baer 9f525876f4 Fix instance-wide actor webfinger lookup
This skips the silenced-user check.

Ref T820
2021-03-08 13:02:59 -05:00
Matt Baer 9b336dee8c Fix instance-wide actor lookup
This skips the silenced-user check.

Ref T820
2021-03-08 12:54:50 -05:00
Matt Baer 9aeeb52bdb Fix nil pointer on instance-wide actor lookup
Ref T820
2021-03-08 12:50:08 -05:00
Matt Baer 9484880bca Sign actor fetch request
This fixes federation with Mastodon instances that have Authorized
Fetch turned on by signing the GET request to fetch the actor when
a blog is first followed.

Ref T820
2021-03-08 11:43:38 -05:00
Colin Axnér f2e3cd8bd7 merge develop 2021-03-08 11:42:00 +01:00
Colin Axnér 00f2152c2b update commit for web-core
Update go.mod to use latest commit on web-core
2021-03-08 11:37:22 +01:00
Matt Baer 4cf9500704
Merge pull request #418 from writeas/consistent-feeds
Use HTMLContent field in RSS feed
2021-03-06 17:27:34 -05:00
Matt Baer fbb67bc9ef
Merge pull request #383 from writeas/wysiwyg
Add WYSIWYG editor option
2021-03-06 16:48:56 -05:00
Matt Baer 4f32af2d7f
Merge pull request #220 from writeas/support-notes
Support ActivityPub Notes
2021-03-05 11:10:19 -05:00
Matt Baer 97242cd5ec
Merge pull request #433 from writeas/dependabot/go_modules/github.com/stretchr/testify-1.7.0
Bump github.com/stretchr/testify from 1.6.1 to 1.7.0
2021-03-05 08:59:24 -05:00
Matt Baer bd77145bf3
Merge pull request #423 from writeas/dependabot/go_modules/github.com/mattn/go-sqlite3-1.14.6
Bump github.com/mattn/go-sqlite3 from 1.14.4 to 1.14.6
2021-03-04 17:05:52 -05:00
dependabot[bot] 1ea728b1e9
Bump github.com/stretchr/testify from 1.6.1 to 1.7.0
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.6.1 to 1.7.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.6.1...v1.7.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-04 18:03:38 +00:00
Matt Baer c813d08230
Merge pull request #414 from writeas/dependabot/go_modules/github.com/urfave/cli/v2-2.3.0
Bump github.com/urfave/cli/v2 from 2.2.0 to 2.3.0
2021-03-04 13:02:06 -05:00
dependabot[bot] 1ac5c4ab4d
Bump github.com/mattn/go-sqlite3 from 1.14.4 to 1.14.6
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.4 to 1.14.6.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.4...v1.14.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-04 16:46:50 +00:00
Matt Baer ff976a950e
Merge pull request #409 from writeas/dependabot/go_modules/github.com/fatih/color-1.10.0
Bump github.com/fatih/color from 1.9.0 to 1.10.0
2021-03-04 11:45:28 -05:00
Matt Baer 1f6d0e2e70 Merge branch 'develop' into support-notes 2021-03-04 11:42:49 -05:00
dependabot[bot] 5b2c350b5d
Bump github.com/fatih/color from 1.9.0 to 1.10.0
Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.9.0 to 1.10.0.
- [Release notes](https://github.com/fatih/color/releases)
- [Commits](https://github.com/fatih/color/compare/v1.9.0...v1.10.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-04 00:22:20 +00:00
Matt Baer b3dd06c79b Merge branch 'develop' into wysiwyg 2021-03-03 17:38:28 -05:00
dependabot[bot] 71b211b11e
Bump github.com/urfave/cli/v2 from 2.2.0 to 2.3.0
Bumps [github.com/urfave/cli/v2](https://github.com/urfave/cli) from 2.2.0 to 2.3.0.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v2.2.0...v2.3.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-03 22:37:46 +00:00
Matt Baer 33d47ca420
Merge pull request #408 from writeas/dependabot/go_modules/gopkg.in/ini.v1-1.62.0
Bump gopkg.in/ini.v1 from 1.61.0 to 1.62.0
2021-03-03 17:36:30 -05:00
Matt Baer c2c6b69044 Update prose.bundle.js 2021-03-03 17:05:05 -05:00
Matt Baer 706ae9cc77 Style Read More section in WYSIWYG like link 2021-03-03 17:04:16 -05:00
Matt Baer 1abc9b643f Improve WYSIWYG editor modal style 2021-03-03 16:41:21 -05:00
Matt Baer 8a8288d2af Build ProseMirror library on `make ui` 2021-03-03 16:39:29 -05:00
Matt Baer e36e39cb73 Fix WYSIWYG editor script in Chrome <85
This uses String.replace() instead of String.replaceAll().
2021-03-03 16:38:49 -05:00
v 583693ed8d Updated prod prosemirror bundle. 2021-03-03 20:45:55 +01:00
v 19beabe2d1 Fixed Safari regex lookahead / lookbehind issue. 2021-03-03 20:29:23 +01:00
Donald Feury ebdb932090 I meant develop, not master 2021-02-24 23:57:35 -05:00
Donald Feury 4c0fcdf7c6 Setting activitypub.go back to master version 2021-02-24 23:55:56 -05:00
Donald Feury 9ed2687543 Added TagCollectionPage
* Implements PrevPageURL and NextPageURL
* This allows the collection-tag template to get proper urls for
  paginating using tags.
2021-02-24 23:49:15 -05:00
Donald Feury 530439772d Add Pagination to Tags Collection
Mostly copied the logic for pagination from non tag collection
2021-02-24 23:00:21 -05:00
Matt Baer 33cf9263f5 Support "nosig" shortcode to hide signature in post
If a post contains <!--nosig--> anywhere in the body, the post will render
without a post signature on it.

Ref T815
2021-02-24 12:49:28 -05:00
Matt Baer a10827cd50 Hide post signature on pinned posts
Ref T814
2021-02-23 17:36:35 -05:00
Matt Baer 65caaca659 Update ProseMirror install instructions 2021-02-23 16:54:38 -05:00
Matt Baer 2d38e8b65e Create coll post with the provided slug, if exists
Closes T811
2021-02-22 14:25:18 -05:00
Viktor Vaczi 8c0978419f using a branch from github with the markdown hashtag serializing fix 2021-02-13 13:27:15 +01:00
CJ Eller 391844fab9
Add conditional for preserving lang metadata
This makes it so that if a post is updated, it will retain the language metadata rather than revert back.
2021-02-10 18:01:32 -05:00
CJ Eller e6c36fc2ef
Update writefreely-web image
Updated with the official writeas/writefreely image on Docker Hub.
2021-02-01 12:01:49 -05:00
Donald Feury e6417d911c Stop private and protected blogs from federating
Fixes #403
2021-01-27 19:39:46 -05:00
x4e 795748457c
Case insensitive language highlighting matching
This automatically lowercases language names used in code blocks when finding highlighting scripts for them, since highlightJS defines all languages with lowercase names.
2021-01-24 01:24:12 +00:00
funkyduck 6c1ab93717 The gopher integration was not setting host or port, causing all links to break.
This will derive the host from the configured host by stripping the protocol from the URI
2021-01-14 09:46:20 -05:00
Viktor Vaczi 6049213661 added prettierrc 2021-01-08 01:41:36 +01:00
Viktor Vaczi 9a55d38e4b working newlines, link shortcut 2021-01-08 00:33:35 +01:00
Viktor Vaczi 676b673c94 Correct line ends 2021-01-05 15:06:19 +01:00
Viktor Vaczi b1cea637cb Removed unnecessary schema nodes, custom markdown parser/serializer, publish keyboard shortcut, readmore node 2021-01-04 17:19:24 +01:00
Matt Baer f31e4d650d Use HTMLContent field in RSS feed
This re-uses the HTMLContent feed for the full HTML content in the RSS
feed, instead of again generating HTML from Markdown.

This keeps things more consistent throughout the application and reduces
work when rendering the feed.
2020-12-15 09:33:14 -05:00
Matt Baer 53ea85dc86
Merge pull request #407 from conor-f/docker-fixes
Dockerfile/docker-compose fixes
2020-12-08 09:38:22 -05:00
Colin Axner fcf01a6039 add back else clause
Add back else clause after realizing the error check doesn't return after logging.
2020-12-07 11:28:51 +01:00
Colin Axner 30fc088cec reset cache after silencing user 2020-12-06 12:45:45 +01:00
Colin Axner 3aa621ee36 allow cache to be forcibly reset
Modify updateTimelineCache to allow a boolean to indicate that the cache should be forcibly reset
2020-12-06 12:30:54 +01:00
Matt Baer d52e2826f8
Merge pull request #389 from colin-axner/remove-unnecessary-var
remove unnecessary var in account.go
2020-11-16 12:06:47 -05:00
dependabot[bot] ed00417d8d
Bump gopkg.in/ini.v1 from 1.61.0 to 1.62.0
Bumps [gopkg.in/ini.v1](https://github.com/go-ini/ini) from 1.61.0 to 1.62.0.
- [Release notes](https://github.com/go-ini/ini/releases)
- [Commits](https://github.com/go-ini/ini/compare/v1.61.0...v1.62.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-01 05:31:06 +00:00
Conor Flynn 9f925c8138 Changes docker-compose image to use writefreely. 2020-10-26 13:52:04 +00:00
Conor Flynn 0eb1a2deec Fixes broken Docker/docker-compose structures.
Updates versions and uses maria DB instead of sqlite in the
docker-compose. Also fixes things related to networks, generating
config, etc.
2020-10-26 13:50:11 +00:00
Darius Kazemi b262fa144c Making changes per feedback 2020-10-22 13:10:51 -07:00
Darius Kazemi 0aafd0c368 Generic OAuth userinfo properies now configurable
When connecting to a generic OAuth provider, you are never sure what object you'll be receiving from the userinfo endpoint (it isn't actually specified anywhere). So this commit adds mapping values to the generic oauth configuration section of config.ini, allowing the user to specify which keys in the remote endpoint it expects to read the UserID, Username, DisplayName, and Email from. Default values if unspecified remain as they were before this commit.
2020-10-22 12:15:55 -07:00
Matt Baer 3493921837
Merge pull request #402 from dariusk/dariusk/mastodon
Adding scope field to generic OAuth
2020-10-13 12:16:32 -04:00
Matt Baer 7d4df23d3c
Merge pull request #397 from mrvdb/issue305
Drop the /tags/{tag} route
2020-10-13 08:55:04 -04:00
Matt Baer 3b91400b62
Merge pull request #387 from writeas/resize-customize-editor
Automatically resize CSS editor
2020-10-13 08:51:05 -04:00
Matt Baer bb008aa66c
Merge pull request #382 from Antolius/fix-post-summary-escaping
Fix escaping in post summary
2020-10-13 08:41:25 -04:00
Darius Kazemi 667cbb97ed Adding scope field to generic OAuth
Some OAuth providers (like Mastodon) do not use the default
"read_user" scope, instead offering a custom scope. The config.ini
for generic OAuth now contains a "scope" field, allowing the admin
to set the scope manually (it defaults to "read_user" if blank).
2020-10-12 20:54:48 -07:00
Matt Baer e1cde913e2
Merge pull request #369 from writeas/web-monetization
Support Web Monetization
2020-10-06 16:22:28 -04:00
Matt Baer 345313200e
Merge pull request #385 from writeas/fix-pinned-post-header-space
Fix header spacing on pinned posts
2020-10-03 11:52:44 -04:00
Matt Baer 211b02c209
Merge pull request #364 from Obayanju/fix-youtube-query-parameters
Fix removal of query parameters on youtube embed links
2020-10-03 11:48:30 -04:00
Matt Baer b1e22795b1
Merge pull request #378 from writeas/dependabot/go_modules/github.com/gorilla/mux-1.8.0
Bump github.com/gorilla/mux from 1.7.4 to 1.8.0
2020-10-03 11:38:53 -04:00
Matt Baer cf0403d955
Merge pull request #398 from writeas/dependabot/go_modules/github.com/mattn/go-sqlite3-1.14.4
Bump github.com/mattn/go-sqlite3 from 1.14.2 to 1.14.4
2020-10-03 11:37:03 -04:00
Matt Baer c1aed45388
Merge pull request #396 from writeas/dependabot/go_modules/gopkg.in/ini.v1-1.61.0
Bump gopkg.in/ini.v1 from 1.57.0 to 1.61.0
2020-10-03 11:33:34 -04:00
dependabot[bot] 083d8c4d67
Bump github.com/mattn/go-sqlite3 from 1.14.2 to 1.14.4
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.2 to 1.14.4.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.2...v1.14.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-03 14:35:04 +00:00
Matt Baer e3c7a8ac3a
Merge pull request #395 from writeas/dependabot/go_modules/github.com/mitchellh/go-wordwrap-1.0.1
Bump github.com/mitchellh/go-wordwrap from 1.0.0 to 1.0.1
2020-10-03 10:33:30 -04:00
dependabot[bot] 454e781ed4
Bump github.com/mitchellh/go-wordwrap from 1.0.0 to 1.0.1
Bumps [github.com/mitchellh/go-wordwrap](https://github.com/mitchellh/go-wordwrap) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/mitchellh/go-wordwrap/releases)
- [Commits](https://github.com/mitchellh/go-wordwrap/compare/v1.0.0...v1.0.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-03 14:30:24 +00:00
Matt Baer 1b6f9b6742
Merge pull request #393 from writeas/dependabot/go_modules/github.com/manifoldco/promptui-0.8.0
Bump github.com/manifoldco/promptui from 0.7.0 to 0.8.0
2020-10-03 10:29:03 -04:00
Marcel van der Boom 5961eb8f27 Drop the /tags/{tag} route
fixes issue #305
2020-10-03 15:34:44 +02:00
dependabot[bot] f5f28550fb
Bump gopkg.in/ini.v1 from 1.57.0 to 1.61.0
Bumps [gopkg.in/ini.v1](https://github.com/go-ini/ini) from 1.57.0 to 1.61.0.
- [Release notes](https://github.com/go-ini/ini/releases)
- [Commits](https://github.com/go-ini/ini/compare/v1.57.0...v1.61.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-01 05:23:13 +00:00
dependabot[bot] c22a751ab7
Bump github.com/manifoldco/promptui from 0.7.0 to 0.8.0
Bumps [github.com/manifoldco/promptui](https://github.com/manifoldco/promptui) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/manifoldco/promptui/releases)
- [Changelog](https://github.com/manifoldco/promptui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/manifoldco/promptui/compare/v0.7.0...v0.8.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-01 05:22:43 +00:00
Matt Baer 2768ea9414 Make Monetization optional
Some WriteFreely instances are completely private, and thus have no need for public-
oriented features like Web Monetization. Like federation, this gives admins control
over whether or not the feature is enabled for users.

Ref T773
2020-09-30 15:18:21 -04:00
Matt Baer 13a3a68d54 Validate and trim spaces on WM pointer
Ref T773
2020-09-30 14:42:11 -04:00
Matt Baer ec7b299fd3 Enable updating WM payment pointer via API and Customize page
Ref T773
2020-09-30 14:40:13 -04:00
Matt Baer f534ee1dec
Merge pull request #391 from colin-axner/getCollectionPage-cleanup
update getCollectionPage
2020-09-29 10:52:19 -04:00
Colin Axner 678653ac30 update getCollectionPage
Update getCollectionPage godoc and reduce logic and variable assignments
2020-09-25 16:47:31 +02:00
Colin Axner 75a79d49bd remove unnecessary var
Remove createdWithPass var in account.go along with impossible if statement
2020-09-25 16:07:30 +02:00
Matt Baer 2908080b52 Automatically resize CSS editor
Closes T708
2020-09-24 15:08:08 -04:00
Matt Baer d6d510aec9 Fix `dated` header class on pinned posts
The header shouldn't include the `dated` property, since we don't display a date
on pinned posts. This fixes that.
2020-09-17 14:55:10 -04:00
Colin Axner 5ba0ea2b04 fix accessibility of silenced user posts
Change view post collection queries to verify that the authenticated user of a silenced collection is either the owner or admin
2020-09-17 12:50:06 +02:00
Matt Baer a96d4474ef Add auto-save to WYSIWYG editor 2020-09-09 17:46:47 -04:00
Matt Baer a7190795f7 Output prose.bundle.js to static/js/ dir, instead of dist/ 2020-09-09 10:47:01 -04:00
Matt Baer 70dbfcfba4 Fix webpack build issues
This upgrades @babel/preset-env to 7.9.0 from 7.8.7.

See: https://github.com/nodejs/node/issues/32852#issuecomment-613655150
2020-09-09 10:45:04 -04:00
Matt Baer da8c08668f Fix whitespace in prose.js 2020-09-09 10:02:00 -04:00
Matt Baer 61daca2b0d Merge branch 'develop' into wysiwyg 2020-09-09 09:53:38 -04:00
Dami f847ade1ef Use camelCase 2020-09-09 00:01:32 -06:00
Dami 3a789f5a00 Go to next regex match if url parsing error 2020-09-08 23:59:56 -06:00
Dami 79715891fb Merge branch 'develop' of https://github.com/writeas/writefreely into fix-youtube-query-parameters 2020-09-08 23:49:26 -06:00
Josip Antoliš eb76faa129 Fix escaping in post summary
Unescaping post content after sanitizing it. This will prevent double
escaping when summary is rendered by html/template package which does
escaping by default.
Fixes #340
2020-09-05 01:59:44 +02:00
Matt Baer 7c1244e6b1
Merge pull request #381 from writeas/fix-testpostlede
Truncate post lede at question mark
2020-09-04 16:36:14 -04:00
Matt Baer c31a87fb76
Merge pull request #336 from Antolius/static-assets-cache-control
Add Cache-Control header
2020-09-04 16:12:29 -04:00
Josip Antoliš 1b1d3064c9 Move cacheControl func into handle.go file 2020-09-04 22:03:42 +02:00
Josip Antoliš 3f36ede885 Add Cache-Control header
Add Cache-Control response header for static files in order to improve page loading speed.
Fixes  #322
2020-09-04 22:03:42 +02:00
Matt Baer f821ead3a1 Support only federating Notes
When setting `notes_only = true` in the `[app]` configuration section, WF will only send out `Note` objects. Otherwise, it will send out both `Note`s and `Article`s.
2020-09-02 10:36:00 -04:00
Matt Baer 8be71481c8 Merge branch 'develop' into support-notes 2020-09-02 10:29:59 -04:00
Matt Baer a8a6525006
Merge pull request #377 from writeas/dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.4
Bump github.com/microcosm-cc/bluemonday from 1.0.3 to 1.0.4
2020-09-02 10:20:30 -04:00
Matt Baer 98d88b9a4b Truncate lede at question mark
This fixes TestPostLede and closes #316.
2020-09-02 09:38:02 -04:00
Matt Baer ac90cb2c80 Fix panic in TestViewOauthCallback 2020-09-02 09:33:16 -04:00
dependabot[bot] 00a5a4f7ab
Bump github.com/microcosm-cc/bluemonday from 1.0.3 to 1.0.4
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.3 to 1.0.4.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.3...v1.0.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-01 19:42:11 +00:00
Matt Baer 505b124db7
Merge pull request #379 from writeas/dependabot/go_modules/github.com/mattn/go-sqlite3-1.14.2
Bump github.com/mattn/go-sqlite3 from 1.14.0 to 1.14.2
2020-09-01 15:40:35 -04:00
Matt Baer f75d4cb75d
Merge pull request #327 from Dar13/add-cors-headers-public-api
Add CORS header ('Access-Control-Allow-Origin') to public APIs
2020-09-01 15:01:33 -04:00
dependabot[bot] 21579cfa71
Bump github.com/mattn/go-sqlite3 from 1.14.0 to 1.14.2
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.0 to 1.14.2.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.0...v1.14.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-01 13:41:01 +00:00
dependabot[bot] 1779aeaf8c
Bump github.com/gorilla/mux from 1.7.4 to 1.8.0
Bumps [github.com/gorilla/mux](https://github.com/gorilla/mux) from 1.7.4 to 1.8.0.
- [Release notes](https://github.com/gorilla/mux/releases)
- [Commits](https://github.com/gorilla/mux/compare/v1.7.4...v1.8.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-01 13:40:59 +00:00
Matt Baer 62d29166f4
Merge pull request #376 from writeas/dependabot/go_modules/github.com/gorilla/schema-1.2.0
Bump github.com/gorilla/schema from 1.1.0 to 1.2.0
2020-09-01 09:39:26 -04:00
dependabot[bot] e60398f0b4
Bump github.com/gorilla/schema from 1.1.0 to 1.2.0
Bumps [github.com/gorilla/schema](https://github.com/gorilla/schema) from 1.1.0 to 1.2.0.
- [Release notes](https://github.com/gorilla/schema/releases)
- [Commits](https://github.com/gorilla/schema/compare/v1.1.0...v1.2.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-01 06:32:10 +00:00
Matt Baer ce69117c79
Merge pull request #371 from writeas/design-update
Navigation improvements
2020-08-31 18:21:41 -05:00
Matt Baer d8019bba0d
Merge pull request #370 from writeas/oauth-cleanup
OAuth tidy-up and refactor
2020-08-31 17:08:33 -05:00
Matt Baer 820c5ae557 Remove Admin link from header on Account Settings page 2020-08-19 18:22:11 -04:00
Matt Baer 3a915ad8ea Make New Post button contextual on coll sub-sections 2020-08-19 18:21:53 -04:00
Matt Baer 8d27ee6d99 Only show "New Post" button in coll. nav bar on Blogs page 2020-08-19 18:20:26 -04:00
Matt Baer 6f8d70043f Move coll. navigation to main header for single-user sites
This uses the main navigation bar for the Customize and Stats pages, instead of the collection-navigation bar used for multi-user / multi-collection instances.
2020-08-19 18:19:28 -04:00
Matt Baer 9d0ba2bed4 Add contextual blog menu 2020-08-19 17:09:47 -04:00
Matt Baer cef51a7797 Remove accidental #user-nav color 2020-08-19 16:56:26 -04:00
Matt Baer 0ed9c9c746 Add inter-blog navigation
This makes it easier to navigate between different blog sections: Customize, Stats, View.
2020-08-19 16:24:37 -04:00
Matt Baer 217430e56b Redirect user to /me/settings on cancelled OAuth flow 2020-08-19 15:40:07 -04:00
Matt Baer 7a09a47de2 Include OAuth buttons on signup-via-invite page 2020-08-19 15:20:13 -04:00
Matt Baer 455e50c9a8 Use branded OAuth buttons
This includes the platform mark with each login button and uses
brand colors. It also uses the same style on the Account Settings
page. And it wraps buttons on login / signup pages.
2020-08-19 15:14:29 -04:00
Matt Baer a78b36b871 Fix whitespace in user/settings.tmpl 2020-08-19 14:55:16 -04:00
Matt Baer 00cceca104 Update signup-via-invite page
This updates signup.tmpl to include all supported OAuth methods and respect the new `DisablePasswordAuth` config value.
2020-08-19 13:35:21 -04:00
Matt Baer 4db2cb8986 Templatize OAuth buttons across signup and login pages
This moves fields into the `OAuthButtons` struct and puts the buttons into templates/includes/oauth.tmpl.
2020-08-19 13:31:07 -04:00
Matt Baer a773d94dc7 Reorder Gitea fields and structs in config.go 2020-08-19 13:26:15 -04:00
Matt Baer 04d404e61f Move text OAuth button styles to login.less 2020-08-19 12:38:44 -04:00
Matt Baer 21e9b4a667 Run `go fmt` on database.go 2020-08-19 12:29:23 -04:00
Matt Baer 63f023ea98 Fix GitLab & OAuth button labels on Login page
Previously, these display names were swapped.
2020-08-19 12:04:36 -04:00
Matt Baer ab32caa49c Include key names in Login page struct 2020-08-19 12:02:36 -04:00
Matt Baer 13eb51913e Support Web Monetization via backend attribute
This supports a new `monetization_pointer` collection attribute.
When present, we include the `monetization` meta tag on all
collection pages.
2020-08-19 09:28:44 -04:00
Matt Baer 95273697f4 Use consistent server User-Agent across application 2020-08-18 12:22:04 -04:00
Matt Baer dfa14c9c92
Merge pull request #317 from pascoual/feature/generic-oauth
Login with generic oauth feature++
2020-08-17 14:24:50 -04:00
prichier ab285644a0 Fix: signup methods mutually exclusive 2020-08-16 20:42:55 +02:00
Pascal Richier d3f1e40010
Merge branch 'develop' into feature/generic-oauth 2020-08-16 19:58:01 +02:00
Matt Baer 7e3eb9a87b
Merge pull request #319 from writeas/silo-mentions
Support AP-style mentions of centralized social media accounts
2020-08-13 12:36:42 -04:00
Matt Baer 7fa78c2255 Move fakeAPInstances to web-core/silobridge package
This adds support for mentioning profiles on the following sites:

- deviantart.com
- facebook.com
- flickr.com
- github.com
- instagram.com
- reddit.com
- wattpad.com
- youtube.com
2020-08-13 12:33:35 -04:00
Matt Baer c16414843a
Merge pull request #368 from writeas/RJ722-217-fix-h2-size
Use a bigger font size for h2
2020-08-13 12:24:41 -04:00
Matt Baer b2382b5422
Merge pull request #293 from gytisrepecka/oauth-gitea
Added Gitea OAuth login and account management.
2020-08-13 10:41:29 -04:00
gytisrepecka 731d4e8efe
Run gofmt for modified files. 2020-08-13 09:15:04 +03:00
Matt Baer fd3a6399b3
Merge pull request #355 from writeas/accessible-dropdowns
Add menu hover delay
2020-08-12 13:51:08 -04:00
Matt Baer 8b243e119f Change post page h2 size to 1.4em 2020-08-12 13:48:48 -04:00
Matt Baer 0c8b779afb Merge branch '217-fix-h2-size' of https://github.com/RJ722/writefreely into RJ722-217-fix-h2-size 2020-08-12 13:42:57 -04:00
Matt Baer 5f52c23a65
Merge pull request #181 from writeas/mathjax-in-binaries
Mathjax in binaries
2020-08-11 09:47:16 -04:00
Matt Baer e37bec6aa1
Merge pull request #196 from writeas/dark-mode-pad
Dark mode pad
2020-08-11 09:30:07 -04:00
Matt Baer 121d83d94d Clean up pad JS formatting, debug messages 2020-08-11 09:27:50 -04:00
Dami 9b614bc922 Fix removal of query parameters on youtube embed links
This uses go's html and url parser plus regex, instead of
using only a single regex for simplicity sake. A single regex expression
might be error prone, for example, when trying to matching html entities.

Fixes #328
2020-08-07 00:05:43 -06:00
Matt Baer 09e70e07f8 Support loading more draft posts
This adds a "load more" button to the bottom of the draft posts page,
which calls /api/me/posts with new parameters and the current page
number. It then populates the page accordingly.

Ref T696 - load anon. posts with ?anonymous=1&page=1
Ref T401 - completes UI for post loading
2020-07-30 16:46:01 -04:00
Matt Baer 7eeba4dc9e Limit initial draft post loading to 10 posts
Ref T401
2020-07-30 16:28:21 -04:00
Matt Baer 849e5b8503
Merge pull request #330 from writeas/post-signatures
Support post signatures

Ref T582
2020-07-30 11:53:27 -04:00
Matt Baer fee44e7c8d Add menu hover delay on user pages + editor 2020-07-30 11:26:29 -04:00
Matt Baer a32fc44153
Merge pull request #350 from writeas/dependabot/go_modules/github.com/fatih/color-1.9.0
Bump github.com/fatih/color from 1.7.0 to 1.9.0
2020-07-28 15:14:54 -04:00
Matt Baer bd387c6dec Merge branch 'develop' into dependabot/go_modules/github.com/fatih/color-1.9.0 2020-07-28 15:09:19 -04:00
Matt Baer cd6ccd257b
Merge pull request #343 from writeas/dependabot/go_modules/github.com/manifoldco/promptui-0.7.0
Bump github.com/manifoldco/promptui from 0.3.2 to 0.7.0
2020-07-28 15:05:21 -04:00
dependabot[bot] 9c835a2b9d
Bump github.com/fatih/color from 1.7.0 to 1.9.0
Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.7.0 to 1.9.0.
- [Release notes](https://github.com/fatih/color/releases)
- [Commits](https://github.com/fatih/color/compare/v1.7.0...v1.9.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 18:01:42 +00:00
Matt Baer 55ffb86ac2
Merge pull request #351 from writeas/dependabot/go_modules/github.com/go-sql-driver/mysql-1.5.0
Bump github.com/go-sql-driver/mysql from 1.4.1 to 1.5.0
2020-07-28 14:00:05 -04:00
Matt Baer 78e0d98589
Merge pull request #349 from writeas/dependabot/go_modules/github.com/guregu/null-3.5.0incompatible
Bump github.com/guregu/null from 3.4.0+incompatible to 3.5.0+incompatible
2020-07-28 13:54:58 -04:00
Matt Baer 6af24293d1 Merge branch 'develop' into dependabot/go_modules/github.com/guregu/null-3.5.0incompatible 2020-07-28 13:53:36 -04:00
Matt Baer 9d7783f80d
Merge pull request #348 from writeas/dependabot/go_modules/gopkg.in/ini.v1-1.57.0
Bump gopkg.in/ini.v1 from 1.55.0 to 1.57.0
2020-07-28 13:41:04 -04:00
Matt Baer 2eaf7493d7
Merge pull request #347 from writeas/dependabot/go_modules/github.com/urfave/cli/v2-2.2.0
Bump github.com/urfave/cli/v2 from 2.1.1 to 2.2.0
2020-07-28 13:19:43 -04:00
Matt Baer ab6d4bfb9d
Merge pull request #353 from writeas/dependabot/go_modules/github.com/writeas/import-0.2.1
Bump github.com/writeas/import from 0.2.0 to 0.2.1
2020-07-28 13:00:25 -04:00
dependabot[bot] 2c45307107
Bump github.com/urfave/cli/v2 from 2.1.1 to 2.2.0
Bumps [github.com/urfave/cli/v2](https://github.com/urfave/cli) from 2.1.1 to 2.2.0.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v2.1.1...v2.2.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:43:05 +00:00
dependabot[bot] ad2e46cb40
Bump github.com/manifoldco/promptui from 0.3.2 to 0.7.0
Bumps [github.com/manifoldco/promptui](https://github.com/manifoldco/promptui) from 0.3.2 to 0.7.0.
- [Release notes](https://github.com/manifoldco/promptui/releases)
- [Changelog](https://github.com/manifoldco/promptui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/manifoldco/promptui/compare/v0.3.2...v0.7.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:40:57 +00:00
Matt Baer e796331de8
Merge pull request #346 from writeas/dependabot/go_modules/github.com/stretchr/testify-1.6.1
Bump github.com/stretchr/testify from 1.6.0 to 1.6.1
2020-07-28 12:39:26 -04:00
Matt Baer 9ff54f9944
Merge pull request #342 from writeas/dependabot/go_modules/github.com/mattn/go-sqlite3-1.14.0
Bump github.com/mattn/go-sqlite3 from 1.10.0 to 1.14.0
2020-07-28 12:38:13 -04:00
dependabot[bot] aa170d0c5a
Bump github.com/mattn/go-sqlite3 from 1.10.0 to 1.14.0
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.10.0 to 1.14.0.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.10.0...v1.14.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:25:28 +00:00
Matt Baer 42c6f3ca03
Merge pull request #345 from writeas/dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.3
Bump github.com/microcosm-cc/bluemonday from 1.0.2 to 1.0.3
2020-07-28 12:22:47 -04:00
Matt Baer 191eac77ab
Merge pull request #352 from writeas/dependabot/go_modules/github.com/gorilla/schema-1.1.0
Bump github.com/gorilla/schema from 1.0.2 to 1.1.0
2020-07-28 12:15:52 -04:00
dependabot[bot] 00c47fa62f
Bump github.com/gorilla/schema from 1.0.2 to 1.1.0
Bumps [github.com/gorilla/schema](https://github.com/gorilla/schema) from 1.0.2 to 1.1.0.
- [Release notes](https://github.com/gorilla/schema/releases)
- [Commits](https://github.com/gorilla/schema/compare/v1.0.2...v1.1.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:10:53 +00:00
Matt Baer 29dc53aacd
Merge pull request #344 from writeas/dependabot/go_modules/github.com/hashicorp/go-multierror-1.1.0
Bump github.com/hashicorp/go-multierror from 1.0.0 to 1.1.0
2020-07-28 12:09:22 -04:00
Matt Baer 2f06b0b487
Merge pull request #341 from writeas/dependabot/go_modules/github.com/gorilla/feeds-1.1.1
Bump github.com/gorilla/feeds from 1.1.0 to 1.1.1
2020-07-28 12:06:19 -04:00
dependabot[bot] 5897ef7cab
Bump github.com/writeas/import from 0.2.0 to 0.2.1
Bumps [github.com/writeas/import](https://github.com/writeas/import) from 0.2.0 to 0.2.1.
- [Release notes](https://github.com/writeas/import/releases)
- [Commits](https://github.com/writeas/import/compare/v0.2.0...v0.2.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:01:26 +00:00
dependabot[bot] 99b2f41aa1
Bump github.com/go-sql-driver/mysql from 1.4.1 to 1.5.0
Bumps [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) from 1.4.1 to 1.5.0.
- [Release notes](https://github.com/go-sql-driver/mysql/releases)
- [Changelog](https://github.com/go-sql-driver/mysql/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-sql-driver/mysql/compare/v1.4.1...v1.5.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:01:24 +00:00
dependabot[bot] 94094ed16d
Bump github.com/guregu/null
Bumps [github.com/guregu/null](https://github.com/guregu/null) from 3.4.0+incompatible to 3.5.0+incompatible.
- [Release notes](https://github.com/guregu/null/releases)
- [Commits](https://github.com/guregu/null/compare/v3.4.0...v3.5.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:01:19 +00:00
dependabot[bot] f278eccd14
Bump gopkg.in/ini.v1 from 1.55.0 to 1.57.0
Bumps [gopkg.in/ini.v1](https://github.com/go-ini/ini) from 1.55.0 to 1.57.0.
- [Release notes](https://github.com/go-ini/ini/releases)
- [Commits](https://github.com/go-ini/ini/compare/v1.55.0...v1.57.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:01:13 +00:00
dependabot[bot] 267c9df1c4
Bump github.com/stretchr/testify from 1.6.0 to 1.6.1
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.6.0 to 1.6.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.6.0...v1.6.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:01:06 +00:00
dependabot[bot] b569144624
Bump github.com/microcosm-cc/bluemonday from 1.0.2 to 1.0.3
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.2 to 1.0.3.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.2...v1.0.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:01:01 +00:00
dependabot[bot] 3d80b46bdc
Bump github.com/hashicorp/go-multierror from 1.0.0 to 1.1.0
Bumps [github.com/hashicorp/go-multierror](https://github.com/hashicorp/go-multierror) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/hashicorp/go-multierror/releases)
- [Commits](https://github.com/hashicorp/go-multierror/compare/v1.0.0...v1.1.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:00:57 +00:00
dependabot[bot] cfaaffdc6c
Bump github.com/gorilla/feeds from 1.1.0 to 1.1.1
Bumps [github.com/gorilla/feeds](https://github.com/gorilla/feeds) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/gorilla/feeds/releases)
- [Commits](https://github.com/gorilla/feeds/compare/v1.1.0...v1.1.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:00:55 +00:00
Matt Baer 3294087abd
Merge pull request #333 from shleeable/patch-2
Create dependabot.yml
2020-07-28 11:59:43 -04:00
Shlee 48e85b7c63
Update dependabot.yml 2020-07-25 00:10:13 +08:00
Matt Baer 4c5f45f462
Merge pull request #273 from writeas/gopher
Add Gopher support

Closes T559
2020-07-23 11:48:25 -04:00
Matt Baer 6dbc753ecb Merge branch 'develop' into gopher 2020-07-23 11:47:49 -04:00
Matt Baer 1451fc1369 Use latest version of go-gopher library 2020-07-23 11:46:00 -04:00
Matt Baer dbe36861c3 Prevent Gopher server start on Private instance 2020-07-23 11:11:11 -04:00
Matt Baer 24fa3d6863
Merge pull request #301 from joicemjoseph/fix/tls-mysql
fix: #297 - tls for mysql connections
2020-07-23 10:03:49 -04:00
Matt Baer 94fee2e19e
Merge pull request #318 from writeas/ignore-collation-errs
Ignore "collation mix" errors in GetCollectionRedirect()
2020-07-23 10:02:26 -04:00
Matt Baer ede68d86a7
Merge pull request #324 from writeas/fix-unsynced-edits
Show warning in editor when local draft is out of date
2020-07-22 15:46:38 -04:00
Matt Baer 504a2a42aa
Merge pull request #329 from writeas/add-footer
Add footer and header element
2020-07-07 10:11:32 -04:00
Shlee b98903cff8
Update dependabot.yml 2020-07-02 20:28:40 +08:00
Shlee beef2b15a7
Create dependabot.yml 2020-07-02 20:24:26 +08:00
Neil Moore 94bcb91220 Add CORS header ('Access-Control-Allow-Origin') to 'AllReader'
Allows all API endpoints using 'AllReader' to be queryable by any
origin.
2020-06-23 20:33:30 -04:00
Matt Baer a25664bb97 Support post signatures
This enables users to add a signature to all blog posts, and update it from a single location.

Requires database migration with: writefreely db migrate

Closes T582
2020-06-23 16:24:45 -04:00
CJ Eller 591bb0866c Add footer and header element 2020-06-22 20:33:52 +00:00
prichier f6aa99e591 Add disable_password_auth option 2020-06-14 00:27:25 +02:00
Matt Baer 9624c4db00 Show warning in editor when local draft is out of date
Fixes #41
2020-06-11 11:45:12 -04:00
Matt Baer 507acc7e1c Support AP-style mentions of centralized social media accounts
This allows users to mention users on the following non-ActivityPub
social media sites:

- twitter.com
- medium.com

It also adds missing error handling in federatePost().
2020-06-08 13:50:43 -04:00
Matt Baer cceea03076 Ignore "collation mix" errors in GetCollectionRedirect() 2020-06-08 13:37:02 -04:00
prichier 724ab34006 Fix: option name from allow_logout to allow_disconnect 2020-06-06 23:52:26 +02:00
prichier fe7ff38bd8 Manage generic Oauth buttons on Account Settings
Add generic Oauth allow_logout option
2020-05-31 04:09:14 +02:00
Keturah Dola-Borg cd01a4459d Fix login page variable name
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-31 00:29:40 +02:00
Keturah Dola-Borg 405a2602ce Fix endpoint URI generation
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-31 00:20:39 +02:00
Keturah Dola-Borg 92d822b5c6 Remove redundant variable
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-31 00:20:35 +02:00
Keturah Dola-Borg 211d441090 Fix capitalisation of OAuth in display name
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-31 00:20:27 +02:00
Keturah Dola-Borg 7b71d455a8 Apply go fmt
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-31 00:20:21 +02:00
Keturah Dola-Borg 630ac1f7c0 Typo fix
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-31 00:20:16 +02:00
Keturah Dola-Borg badaffcd5c Add generic oauth to routes
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-31 00:20:08 +02:00
Keturah Dola-Borg cfd2165442 Add HTML for generic oauth login button
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-31 00:19:58 +02:00
Keturah Dola-Borg 75ca5cd417 Add generic oauth module
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-30 23:59:02 +02:00
Keturah Dola-Borg ee1ca48800 Add generic oauth client
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-30 23:58:57 +02:00
Keturah Dola-Borg 89f7946cb0 Add config/ini structures for generic oauth
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-30 23:58:51 +02:00
Keturah Dola-Borg 6174987c6a Adds generic oAuth bool & name string to login view.
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-30 23:58:42 +02:00
Matt Baer 5c94d23466
Merge pull request #312 from writeas/fix-no-to
Only log "No to!" when debugging
2020-05-29 05:53:24 -04:00
Matt Baer 2aa154d85c
Merge pull request #295 from writeas/optimize-drafts-fetch
Optimize Drafts retrieval
2020-05-29 05:52:58 -04:00
RJ722 53cb5c3837 core.less: Use a bigger font size for h2
The font-size for h2 was set to be 1.17em, which is also the font
size defined for h3 by user agent stylesheet. This lead to both
h2 and h3 being rendered in same size. Use 1.2em for h2.

Closes: #217
2020-05-25 07:01:34 +05:30
Matt Baer 9d854c17c1 Only log "No to!" when debugging
Fixes #311
2020-05-15 13:48:20 -04:00
Matt Baer 037fc40fb3
Merge pull request #307 from writeas/fix-numeric-alpha-hashtags
Support hashtags with numbers + letters
2020-05-07 11:33:30 -04:00
Matt Baer 5fe1dd1731 Add SQLite query for v9 migration 2020-05-06 14:08:48 -04:00
Matt Baer b9c467558c Return transaction Begin error in v9 migration 2020-05-06 14:08:25 -04:00
Matt Baer a0e517c224 Merge branch 'develop' into optimize-drafts-fetch 2020-05-01 15:00:51 -04:00
Matt Baer dc7b5df90e Update saturday library to support number+letter hashtags
Previously, a hashtag like #100DaysToOffload wouldn't automatically turn
into a clickable link. This fixes that by updating to the latest version
of the satuday library.
2020-04-28 10:50:51 -04:00
gytisrepecka 8675eb0f95
Merge develop branch into oauth-gitea and resolve pages/login.tmpl conflict. 2020-04-23 14:24:17 +03:00
Matt Baer 99d86a7489
Merge pull request #303 from writeas/fix-oauth-account-creation
Respect registration config on OAuth signup flow
2020-04-22 13:49:52 -04:00
Matt Baer 8e16bac12c Update README
Includes a better description, features, install and license sections, and a screenshot.
2020-04-22 13:43:29 -04:00
Matt Baer 7420039770 Merge branch 'develop' of github.com:writeas/writefreely into develop 2020-04-22 12:47:00 -04:00
Matt Baer f15acf3880 Reduce vertical margin around OAuth buttons 2020-04-22 09:27:33 -04:00
Matt Baer 308b1a7282 Remove "login" verbiage on OAuth signup page
Change it to reflect that this is the final step in the signup flow.
2020-04-22 09:27:19 -04:00
Matt Baer fd97539f85 Mention unset password on failed login
(when it applies)
2020-04-22 09:26:42 -04:00
Matt Baer cf3d5588c2 Move unique OAuth username creation to client-side
Now, on OAuth signup form, we create a unique username with random appended string only if there's a conflict.
Previously, this was always happening during the Slack OAuth flow. This has the benefit of preventing username collisions for all OAuth providers.
2020-04-22 09:17:25 -04:00
Matt Baer 6fc166174b
Merge pull request #299 from writeas/fix-friendica-federation
Fix federation with Friendica
2020-04-21 14:39:48 -04:00
Matt Baer 0c6d3e45e4 Update Go modules 2020-04-21 13:04:19 -04:00
Matt Baer b97038e696 Better describe usage stats in setup process 2020-04-21 13:01:39 -04:00
Matt Baer 37ccf69d81 Increase App Settings checkbox and select sizes 2020-04-21 13:01:08 -04:00
Matt Baer 0127e38ed0 Reorder App Settings page and improve descriptions 2020-04-21 13:00:36 -04:00
Matt Baer 7b7df5535e Run go fmt on oauth_test.go 2020-04-21 07:31:23 -04:00
Matt Baer 5400f416c0 Reduce db calls on normal invite-based signup
This removes an unnecessary database call after creating a user, and documents `db.CreateUser()` to make it clear that extra calls are unnecessary.
2020-04-20 18:21:01 -04:00
Matt Baer ca4a576c31 Support OAuth registration with invite code
This adds any OAuth login buttons to the invite signup page, stores the invite code for the flow duration, and associates the new user with it once successfully registered.

It enables invite-only instances with OAuth-based registration.
2020-04-20 18:18:23 -04:00
Matt Baer 93c2773412 Prevent account creation via OAuth when registration is closed 2020-04-20 15:26:53 -04:00
gytisrepecka 0e1459c6b2
Remove address variable - to make Locations use host value from config instead. 2020-04-17 17:12:06 +03:00
gytisrepecka 658310bc24
Remove constant giteaHost because there is no default URL for Gitea - must always use host from config.ini. 2020-04-17 17:07:45 +03:00
gytisrepecka ddd519f6b7 Merge branch 'develop' into oauth-gitea before making fixes to address pull request #293 feedback. 2020-04-17 15:33:18 +03:00
Joice M. Joseph 671c7e99a5 fix: #297 - tls for mysql connetions 2020-04-16 09:34:16 +05:30
Matt Baer 5e4ed5d9bc Remove extraneous @context fields on AP outbox 2020-04-15 12:30:50 -04:00
Matt Baer 1c5a0099b6 Fix empty date showing on collection 404 page 2020-04-14 07:27:44 -04:00
Matt Baer 5de4d2086b Optimize Drafts retrieval
This adds a database index to speed up retrieval of Drafts.

It is untested with SQLite.
2020-04-09 13:54:26 -04:00
Matt Baer e51e58386e Update versions in migrations.go 2020-04-09 13:49:44 -04:00
Matt Baer 9f1dd7a138 Use latest writeas/activityserve library
This fixes a 500 error / panic caused by ActivityPub actors without an
`inbox` or `outbox` attribute.
2020-04-09 13:00:29 -04:00
gytisrepecka c798a44f69
Added Gitea OAuth login and account management. 2020-04-03 13:26:59 +03:00
Matt Baer d6cb178eb6 Use writeas/impart v1.1.1
This doesn't change the actual underlying dependency; it simply uses the
current, most recent tag for the impart library.
2020-03-28 13:30:44 -04:00
Matt Baer f76bfebfde Add dedicated Title field to WYSIWYG editor
This takes styling from the Classic Editor on Write.as.

It adds all application code for auto-saving the title, publishing it
with the post body, and including it in the word count.
2020-03-11 13:28:02 -04:00
Matt Baer 4b0833435f Restyle WYSIWYG editor
- Match light / dark theme
- Make editor fill the viewport
- Remove borders
- Add spacing in toolbar
2020-03-11 12:42:43 -04:00
Matt Baer 9780f0bbb9 Reformat prose.less 2020-03-11 12:13:24 -04:00
Matt Baer d277e283d5 Move ProseMirror styles to less dir
This keeps it consistent with all our other CSS.
2020-03-11 11:55:55 -04:00
Rob Loranger 7bccb3d7f1
fix not setting value to existing post on edit 2020-03-11 08:38:36 -07:00
Rob Loranger b3a541ab09
markdown preserved between edit/load/save 2020-03-11 07:59:22 -07:00
Rob Loranger ee712bbfaa
WIP: implement WYSIWYG editor w/ prosemirror 2020-03-11 07:18:03 -07:00
Rob Loranger cb1553d67e
add basic prosemirror instructions 2020-03-10 15:17:40 -07:00
Rob Loranger 58f27717be
update .gitignore to ignore node_modules 2020-03-10 15:12:21 -07:00
Rob Loranger f1f5dbb128
add prosemirror build environment 2020-03-10 15:11:32 -07:00
Matt Baer 6aa8de3a4b Add Gopher support
This adds gopher support to WriteFreely -- both single- and multi-user
instances. It is off by default, but can be enabled with the new
`gopher_port` config value in the `[server]` section.

When enabled, multi-user instances will show all public blogs at
gopher://[host]:[gopher_port]/ -- otherwise, blogs are accessible at
gopher://[host]:[gopher_port]/[blog]/

This is just a proof of concept for now. We still need to handle some
edge cases and different configurations, like private instances.

Ref T559
2020-03-01 20:12:47 -05:00
Matt Baer f406f894c5 Merge branch 'develop' into T319-user-delete-acct 2020-02-09 11:53:24 -05:00
Matt Baer d6c0026644 Merge branch 'develop' into T319-admin-delete-acct 2020-02-09 11:51:39 -05:00
Matt Baer 5259c4fcdf Federate posts without a double-line break as Notes
This proof-of-concept sends out posts that don't include a double-line
break as Notes -- otherwise they go out as Articles, as before.

Inspired by the discussion here:
https://socialhub.activitypub.rocks/t/resolving-the-note-vs-article-distinction/258
2019-12-04 19:40:52 -05:00
Rob Loranger fc553d277f
add admin user account deletion
this adds a section to the admin user view to delete the account and a
handler to process the request.
2019-11-05 12:22:58 -08:00
Rob Loranger 482e632ca9
add user account delete UI 2019-11-05 12:22:27 -08:00
Michael Demetriou 638059a26b Remove css changes, keep only pad switching code
https://github.com/writeas/writefreely/pull/188#issuecomment-540045935
2019-10-15 11:53:53 +03:00
Michael Demetriou 8404f0896c Handle default pad color mode according to `prefers-color-scheme`
This just queries the browser whether `prefers-color-scheme` is set
and chooses dark mode if needed, and only if the user hasn't manually
set a scheme by pushing the button.
2019-10-15 11:53:53 +03:00
Michael Demetriou dfa98bcfc8 WIP: this is a branch that attempts to honor the new dark mode
in OS preferences. I will be using it myself and fix things until
I find out that this is adequate to merge to develop.
2019-10-15 11:53:53 +03:00
Rob Loranger 4d150fe831
Revert "add all mathjax components"
This reverts commit 25145296b3.
2019-09-12 10:19:08 -07:00
Rob Loranger 25145296b3
add all mathjax components
with only the single script, there were still many requests to a CDN.
this fixes that and speed up the page load a bit as well.
2019-09-12 10:12:57 -07:00
Rob Loranger 84d7ac35d3
fix issue with fonts and not rendering 2019-09-11 14:04:13 -07:00
Rob Loranger aad4768aed
include mathjax script
this includes a copy of mathjax@3.0.0/es5/tex-mml-chtml.js
2019-09-11 11:18:29 -07:00
Rob Loranger 38c1bf9cab
remove mathjax submodule 2019-09-11 10:57:46 -07:00
191 changed files with 24522 additions and 1391 deletions

View File

@ -1,2 +1 @@
Dockerfile
.git

10
.editorconfig Normal file
View File

@ -0,0 +1,10 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
[*.go]
indent_style = tab

2
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,2 @@
github: writefreely
open_collective: writefreely

15
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,15 @@
version: 2
updates:
- package-ecosystem: "gomod" # See documentation for possible values
directory: "/" # Location of package manifests
open-pull-requests-limit: 50
schedule:
interval: "monthly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "docker"
directory: "/Dockerfile"
schedule:
interval: "daily"

61
.github/workflows/docker-publish.yml vendored Normal file
View File

@ -0,0 +1,61 @@
name: Build container image, publish as GitHub-package
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
on:
push:
branches: [ main, develop ]
# Publish semver tags as releases.
tags:
- 'v*.*.*'
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@v3.0.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v4.6.0
with:
images: |
ghcr.io/${{ github.repository }}
flavor: latest=true
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker images
uses: docker/build-push-action@v5.0.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

2
.gitignore vendored
View File

@ -1,7 +1,9 @@
node_modules
*~
*.swp
*.swo
static/local/custom.css
build
tmp
*.ini

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "static/js/mathjax"]
path = static/js/mathjax
url = https://github.com/mathjax/MathJax.git

View File

@ -4,7 +4,7 @@ Welcome! We're glad you're interested in contributing to WriteFreely.
For **questions**, **help**, **feature requests**, and **general discussion**, please use [our forum](https://discuss.write.as).
For **bug reports**, please [open a GitHub issue](https://github.com/writeas/writefreely/issues/new). See our guide on [submitting bug reports](https://writefreely.org/contribute#bugs).
For **bug reports**, please [open a GitHub issue](https://github.com/writefreely/writefreely/issues/new). See our guide on [submitting bug reports](https://writefreely.org/contribute#bugs).
## Getting Started
@ -80,9 +80,9 @@ We highly value commit messages that follow established form within the project.
When in doubt, look to our existing git history for examples of good commit messages. Here are a few:
* [Rename Suspend status to Silence](https://github.com/writeas/writefreely/commit/7e014ca65958750ab703e317b1ce8cfc4aad2d6e)
* [Show 404 when remote user not found](https://github.com/writeas/writefreely/commit/867eb53b3596bd7b3f2be3c53a3faf857f4cd36d)
* [Fix post deletion on Pleroma](https://github.com/writeas/writefreely/commit/fe82cbb96e3d5c57cfde0db76c28c4ea6dabfe50)
* [Rename Suspend status to Silence](https://github.com/writefreely/writefreely/commit/7e014ca65958750ab703e317b1ce8cfc4aad2d6e)
* [Show 404 when remote user not found](https://github.com/writefreely/writefreely/commit/867eb53b3596bd7b3f2be3c53a3faf857f4cd36d)
* [Fix post deletion on Pleroma](https://github.com/writefreely/writefreely/commit/fe82cbb96e3d5c57cfde0db76c28c4ea6dabfe50)
### Submitting pull requests

View File

@ -1,30 +1,42 @@
# Build image
FROM golang:1.13-alpine as build
# SHA256 of golang:1.21-alpine3.18 linux/amd64
FROM golang@sha256:f475434ea2047a83e9ba02a1da8efc250fa6b2ed0e9e8e4eb8c5322ea6997795 as build
RUN apk add --update nodejs nodejs-npm make g++ git sqlite-dev
RUN npm install -g less less-plugin-clean-css
RUN go get -u github.com/jteeuwen/go-bindata/...
LABEL org.opencontainers.image.source="https://github.com/writefreely/writefreely"
LABEL org.opencontainers.image.description="WriteFreely is a clean, minimalist publishing platform made for writers. Start a blog, share knowledge within your organization, or build a community around the shared act of writing."
RUN apk -U upgrade \
&& apk add --no-cache nodejs npm make g++ git \
&& npm install -g less less-plugin-clean-css \
&& mkdir -p /go/src/github.com/writefreely/writefreely
WORKDIR /go/src/github.com/writefreely/writefreely
RUN mkdir -p /go/src/github.com/writeas/writefreely
WORKDIR /go/src/github.com/writeas/writefreely
COPY . .
RUN cat ossl_legacy.cnf > /etc/ssl/openssl.cnf
ENV GO111MODULE=on
ENV NODE_OPTIONS=--openssl-legacy-provider
RUN make build \
&& make ui
RUN mkdir /stage && \
cp -R /go/bin \
/go/src/github.com/writeas/writefreely/templates \
/go/src/github.com/writeas/writefreely/static \
/go/src/github.com/writeas/writefreely/pages \
/go/src/github.com/writeas/writefreely/keys \
/go/src/github.com/writeas/writefreely/cmd \
&& make ui \
&& mkdir /stage \
&& cp -R /go/bin \
/go/src/github.com/writefreely/writefreely/templates \
/go/src/github.com/writefreely/writefreely/static \
/go/src/github.com/writefreely/writefreely/pages \
/go/src/github.com/writefreely/writefreely/keys \
/go/src/github.com/writefreely/writefreely/cmd \
/stage
# Final image
FROM alpine:3.11
# SHA256 of alpine:3.18.4 linux/amd64
FROM alpine@sha256:48d9183eb12a05c99bcc0bf44a003607b8e941e1d4f41f9ad12bdcc4b5672f86
RUN apk -U upgrade \
&& apk add --no-cache openssl ca-certificates
RUN apk add --no-cache openssl ca-certificates
COPY --from=build --chown=daemon:daemon /stage /go
WORKDIR /go
@ -33,3 +45,6 @@ EXPOSE 8080
USER daemon
ENTRYPOINT ["cmd/writefreely/writefreely"]
HEALTHCHECK --start-period=5s --interval=15s --timeout=5s \
CMD curl -fSs http://localhost:8080/ || exit 1

View File

@ -1,5 +1,5 @@
GITREV=`git describe | cut -c 2-`
LDFLAGS=-ldflags="-X 'github.com/writeas/writefreely.softwareVer=$(GITREV)'"
LDFLAGS=-ldflags="-s -w -X 'github.com/writefreely/writefreely.softwareVer=$(GITREV)'"
GOCMD=go
GOINSTALL=$(GOCMD) install $(LDFLAGS)
@ -14,50 +14,50 @@ TMPBIN=./tmp
all : build
ci: ci-assets deps
ci: deps
cd cmd/writefreely; $(GOBUILD) -v
build: assets deps
cd cmd/writefreely; $(GOBUILD) -v -tags='sqlite'
build: deps
cd cmd/writefreely; $(GOBUILD) -v -tags='netgo sqlite'
build-no-sqlite: assets-no-sqlite deps-no-sqlite
cd cmd/writefreely; $(GOBUILD) -v -o $(BINARY_NAME)
build-no-sqlite: deps-no-sqlite
cd cmd/writefreely; $(GOBUILD) -v -tags='netgo' -o $(BINARY_NAME)
build-linux: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u src.techknowlogick.com/xgo; \
$(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi
xgo --targets=linux/amd64, -dest build/ $(LDFLAGS) -tags='sqlite' -out writefreely ./cmd/writefreely
xgo --targets=linux/amd64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely .
build-windows: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u src.techknowlogick.com/xgo; \
$(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi
xgo --targets=windows/amd64, -dest build/ $(LDFLAGS) -tags='sqlite' -out writefreely ./cmd/writefreely
xgo --targets=windows/amd64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely .
build-darwin: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u src.techknowlogick.com/xgo; \
$(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi
xgo --targets=darwin/amd64, -dest build/ $(LDFLAGS) -tags='sqlite' -out writefreely ./cmd/writefreely
xgo --targets=darwin/amd64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely .
build-arm6: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u src.techknowlogick.com/xgo; \
$(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi
xgo --targets=linux/arm-6, -dest build/ $(LDFLAGS) -tags='sqlite' -out writefreely ./cmd/writefreely
xgo --targets=linux/arm-6, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely .
build-arm7: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u src.techknowlogick.com/xgo; \
$(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi
xgo --targets=linux/arm-7, -dest build/ $(LDFLAGS) -tags='sqlite' -out writefreely ./cmd/writefreely
xgo --targets=linux/arm-7, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely .
build-arm64: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u src.techknowlogick.com/xgo; \
$(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi
xgo --targets=linux/arm64, -dest build/ $(LDFLAGS) -tags='sqlite' -out writefreely ./cmd/writefreely
xgo --targets=linux/arm64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely .
build-docker :
$(DOCKERCMD) build -t $(IMAGE_NAME):latest -t $(IMAGE_NAME):$(GITREV) .
@ -65,8 +65,8 @@ build-docker :
test:
$(GOTEST) -v ./...
run: dev-assets
$(GOINSTALL) -tags='sqlite' ./...
run:
$(GOINSTALL) -tags='netgo sqlite' ./...
$(BINARY_NAME) --debug
deps :
@ -81,11 +81,12 @@ install : build
cmd/writefreely/$(BINARY_NAME) --init-db
cd less/; $(MAKE) install $(MFLAGS)
release : clean ui assets
release : clean ui
mkdir -p $(BUILDPATH)
cp -r templates $(BUILDPATH)
cp -r pages $(BUILDPATH)
cp -r static $(BUILDPATH)
rm -r $(BUILDPATH)/static/local
scripts/invalidate-css.sh $(BUILDPATH)
mkdir $(BUILDPATH)/keys
$(MAKE) build-linux
@ -105,13 +106,13 @@ release : clean ui assets
tar -cvzf $(BINARY_NAME)_$(GITREV)_linux_arm64.tar.gz -C build $(BINARY_NAME)
rm $(BUILDPATH)/$(BINARY_NAME)
$(MAKE) build-darwin
mv build/$(BINARY_NAME)-darwin-10.6-amd64 $(BUILDPATH)/$(BINARY_NAME)
mv build/$(BINARY_NAME)-darwin-10.12-amd64 $(BUILDPATH)/$(BINARY_NAME)
tar -cvzf $(BINARY_NAME)_$(GITREV)_macos_amd64.tar.gz -C build $(BINARY_NAME)
rm $(BUILDPATH)/$(BINARY_NAME)
$(MAKE) build-windows
mv build/$(BINARY_NAME)-windows-4.0-amd64.exe $(BUILDPATH)/$(BINARY_NAME).exe
cd build; zip -r ../$(BINARY_NAME)_$(GITREV)_windows_amd64.zip ./$(BINARY_NAME)
rm $(BUILDPATH)/$(BINARY_NAME)
rm $(BUILDPATH)/$(BINARY_NAME).exe
$(MAKE) build-docker
$(MAKE) release-docker
@ -131,36 +132,14 @@ release-docker :
ui : force_look
cd less/; $(MAKE) $(MFLAGS)
assets : generate
go-bindata -pkg writefreely -ignore=\\.gitignore -tags="!wflib" schema.sql sqlite.sql
assets-no-sqlite: generate
go-bindata -pkg writefreely -ignore=\\.gitignore -tags="!wflib" schema.sql
dev-assets : generate
go-bindata -pkg writefreely -ignore=\\.gitignore -debug -tags="!wflib" schema.sql sqlite.sql
lib-assets : generate
go-bindata -pkg writefreely -ignore=\\.gitignore -o bindata-lib.go -tags="wflib" schema.sql
generate :
@hash go-bindata > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u github.com/jteeuwen/go-bindata/go-bindata; \
fi
cd prose/; $(MAKE) $(MFLAGS)
$(TMPBIN):
mkdir -p $(TMPBIN)
$(TMPBIN)/go-bindata: deps $(TMPBIN)
$(GOBUILD) -o $(TMPBIN)/go-bindata github.com/jteeuwen/go-bindata/go-bindata
$(TMPBIN)/xgo: deps $(TMPBIN)
$(GOBUILD) -o $(TMPBIN)/xgo src.techknowlogick.com/xgo
ci-assets : $(TMPBIN)/go-bindata
$(TMPBIN)/go-bindata -pkg writefreely -ignore=\\.gitignore -tags="!wflib" schema.sql sqlite.sql
clean :
-rm -rf build
-rm -rf tmp

View File

@ -4,17 +4,17 @@
</p>
<hr />
<p align="center">
<a href="https://github.com/writeas/writefreely/releases/">
<img src="https://img.shields.io/github/release/writeas/writefreely.svg" alt="Latest release" />
</a>
<a href="https://goreportcard.com/report/github.com/writeas/writefreely">
<img src="https://goreportcard.com/badge/github.com/writeas/writefreely" alt="Go Report Card" />
<a href="https://github.com/writefreely/writefreely/releases/">
<img src="https://img.shields.io/github/release/writefreely/writefreely.svg" alt="Latest release" />
</a>
<a href="https://travis-ci.org/writeas/writefreely">
<img src="https://travis-ci.org/writeas/writefreely.svg" alt="Build status" />
<img src="https://travis-ci.org/writefreely/writefreely.svg" alt="Build status" />
</a>
<a href="https://github.com/writeas/writefreely/releases/latest">
<img src="https://img.shields.io/github/downloads/writeas/writefreely/total.svg" />
<a href="https://github.com/writefreely/writefreely/releases/latest">
<img src="https://img.shields.io/github/downloads/writefreely/writefreely/total.svg" />
</a>
<a href="https://goreportcard.com/report/github.com/writefreely/writefreely">
<img src="https://goreportcard.com/badge/github.com/writefreely/writefreely" alt="Go Report Card" />
</a>
<a href="https://hub.docker.com/r/writeas/writefreely/">
<img src="https://img.shields.io/docker/pulls/writeas/writefreely.svg" />
@ -22,73 +22,68 @@
</p>
&nbsp;
WriteFreely is a beautifully pared-down blogging platform that's simple on the surface, yet powerful underneath.
WriteFreely is a clean, minimalist publishing platform made for writers. Start a blog, share knowledge within your organization, or build a community around the shared act of writing.
It's designed to be flexible and share your writing widely, so it's built around plain text and can publish to the _fediverse_ via ActivityPub. It's easy to install and light enough to run on a Raspberry Pi.
![](https://writefreely.org/img/screens/pencil-reader.png)
[Try the editor](https://write.as/new)
[Try the writing experience](https://write.as/new)
[Find an instance](https://writefreely.org/instances)
## Features
* Start a blog for yourself, or host a community of writers
* Form larger federated networks, and interact over modern protocols like ActivityPub
* Write on a fast, dead-simple, and distraction-free editor
* [Format text](https://howto.write.as/getting-started) with Markdown
* [Organize posts](https://howto.write.as/organization) with hashtags
* Create [static pages](https://howto.write.as/creating-a-static-page)
* Publish drafts and let others proofread them by sharing a private link
* Create multiple lightweight blogs under a single account
* Export all data in plain text files
* Read a stream of other posts in your writing community
* Build more advanced apps and extensions with the [well-documented API](https://developers.write.as/docs/api/)
* Designed around user privacy and consent
### Made for writing
## Hosting
Built on a plain, auto-saving editor, WriteFreely gives you a distraction-free writing environment. Once published, your words are front and center, and easy to read.
We offer two kinds of hosting services that make WriteFreely deployment painless: [Write.as Pro](https://write.as/pro) for individuals, and [Write.as for Teams](https://write.as/for/teams) for businesses. Besides saving you time and effort, both services directly fund WriteFreely development and ensure the long-term sustainability of our open source work.
### A connected community
### [![Write.as Pro](https://writefreely.org/img/writeas-pro-readme.png)](https://write.as/pro)
Start writing together, publicly or privately. Connect with other communities, whether running WriteFreely, [Plume](https://joinplu.me/), or other ActivityPub-powered software. And bring members on board from your existing platforms, thanks to our OAuth 2.0 support.
Start a personal blog on [Write.as](https://write.as), our flagship instance. Built to eliminate setup friction and preserve your privacy, Write.as helps you start a blog in seconds. It supports custom domains (with SSL) and multiple blogs / pen names per account. [Read more here](https://write.as/pro).
### Intuitive organization
### [![Write.as for Teams](https://writefreely.org/img/writeas-for-teams-readme.png)](https://write.as/for/teams)
Categorize articles [with hashtags](https://writefreely.org/docs/latest/writer/hashtags), and create static pages from normal posts by [_pinning_ them](https://writefreely.org/docs/latest/writer/static) to your blog. Create draft posts and publish to multiple blogs from one account.
[Write.as for Teams](https://write.as/for/teams) gives your organization, business, or [open source project](https://write.as/for/open-source) a clutter-free space to share updates or proposals and build your collective knowledge. We take care of hosting, upgrades, backups, and maintenance so your team can focus on writing.
### International
Blog elements are localized in 20+ languages, and WriteFreely includes first-class support for non-Latin and right-to-left (RTL) script languages.
### Private by default
WriteFreely collects minimal data, and never publicizes more than a writer consents to. Writers can seamlessly create multiple blogs from a single account for different pen names or purposes without publicly revealing their association.
<h2><a href="https://write.as/writefreely"><img src="https://writefreely.org/img/writeas-readme.png" height="32px" alt="Write.as" /></a></h2>
The quickest way to deploy WriteFreely is with [Write.as](https://write.as/writefreely), a hosted service from the team behind WriteFreely. You'll get fully-managed installation, backup, upgrades, and maintenance — and directly fund our free software work ❤️
[**Learn more on Write.as**](https://write.as/writefreely).
## Quick start
WriteFreely has minimal requirements to get up and running — you only need to be able to run an executable.
WriteFreely deploys as a static binary on any platform and architecture that Go supports. Just use our built-in SQLite support, or add a MySQL database, and you'll be up and running!
> **Note** this is currently alpha software. We're quickly moving out of this v0.x stage, but while we're in it, there are no guarantees that this is ready for production use.
For common platforms, start with our [pre-built binaries](https://github.com/writefreely/writefreely/releases/) and head over to our [installation guide](https://writefreely.org/start) to get started.
To get started, head over to our [Getting Started guide](https://writefreely.org/start). For production use, jump to the [Running in Production](https://writefreely.org/start#production) section.
### Packages
## Packages
WriteFreely is available in these package repositories:
You can also find WriteFreely in these package repositories, thanks to our wonderful community!
* [Arch User Repository](https://aur.archlinux.org/packages/writefreely/)
## Documentation
Read our full [documentation on WriteFreely.org](https://writefreely.org/docs). Help us improve by contributing to the [writefreely/documentation](https://github.com/writefreely/documentation) repo.
Read our full [documentation on WriteFreely.org](https://writefreely.org/docs) &mdash; and help us improve by contributing to the [writefreely/documentation](https://github.com/writefreely/documentation) repo.
## Development
Ready to hack on your site? Get started with our [developer guide](https://writefreely.org/docs/latest/developer/setup).
## Docker
Read about using Docker in the [documentation](https://writefreely.org/docs/latest/admin/docker).
Start hacking on WriteFreely with our [developer setup guide](https://writefreely.org/docs/latest/developer/setup). For Docker support, see our [Docker guide](https://writefreely.org/docs/latest/admin/docker).
## Contributing
We gladly welcome contributions to WriteFreely, whether in the form of [code](https://github.com/writeas/writefreely/blob/master/CONTRIBUTING.md#contributing-to-writefreely), [bug reports](https://github.com/writeas/writefreely/issues/new?template=bug_report.md), [feature requests](https://discuss.write.as/c/feedback/feature-requests), [translations](https://poeditor.com/join/project/TIZ6HFRFdE), or [documentation](https://github.com/writefreely/documentation) improvements.
We gladly welcome contributions to WriteFreely, whether in the form of [code](https://github.com/writefreely/writefreely/blob/master/CONTRIBUTING.md#contributing-to-writefreely), [bug reports](https://github.com/writefreely/writefreely/issues/new?template=bug_report.md), [feature requests](https://discuss.write.as/c/feedback/feature-requests), [translations](https://poeditor.com/join/project/TIZ6HFRFdE), or [documentation](https://github.com/writefreely/documentation) improvements.
Before contributing anything, please read our [Contributing Guide](https://github.com/writeas/writefreely/blob/master/CONTRIBUTING.md#contributing-to-writefreely). It describes the correct channels for submitting contributions and any potential requirements.
Before contributing anything, please read our [Contributing Guide](https://github.com/writefreely/writefreely/blob/master/CONTRIBUTING.md#contributing-to-writefreely). It describes the correct channels for submitting contributions and any potential requirements.
## License
Licensed under the AGPL.
Copyright © 2018-2022 [Musing Studio LLC](https://musing.studio) and contributing authors. Licensed under the [AGPL](https://github.com/writefreely/writefreely/blob/develop/LICENSE).

5
SECURITY.md Normal file
View File

@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
To report a vulnerability, send an email to security@writefreely.org.

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018-2019 A Bunch Tell LLC.
* Copyright © 2018-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -13,13 +13,17 @@ package writefreely
import (
"encoding/json"
"fmt"
"github.com/mailgun/mailgun-go"
"github.com/writefreely/writefreely/spam"
"html/template"
"net/http"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"github.com/guregu/null/zero"
@ -27,10 +31,9 @@ import (
"github.com/writeas/web-core/auth"
"github.com/writeas/web-core/data"
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/author"
"github.com/writeas/writefreely/config"
"github.com/writeas/writefreely/page"
"github.com/writefreely/writefreely/author"
"github.com/writefreely/writefreely/config"
"github.com/writefreely/writefreely/page"
)
type (
@ -49,6 +52,7 @@ type (
Separator template.HTML
IsAdmin bool
CanInvite bool
CollAlias string
}
)
@ -86,6 +90,11 @@ func apiSignup(app *App, w http.ResponseWriter, r *http.Request) error {
}
func signup(app *App, w http.ResponseWriter, r *http.Request) (*AuthUser, error) {
if app.cfg.App.DisablePasswordAuth {
err := ErrDisabledPasswordAuth
return nil, err
}
reqJSON := IsJSON(r)
// Get params
@ -145,8 +154,6 @@ func signupWithRegistration(app *App, signup userRegistration, w http.ResponseWr
}
// Handle empty optional params
// TODO: remove this var
createdWithPass := true
hashedPass, err := auth.HashPass([]byte(signup.Pass))
if err != nil {
return nil, impart.HTTPError{http.StatusInternalServerError, "Could not create password hash."}
@ -156,23 +163,19 @@ func signupWithRegistration(app *App, signup userRegistration, w http.ResponseWr
u := &User{
Username: signup.Alias,
HashedPass: hashedPass,
HasPass: createdWithPass,
HasPass: true,
Email: prepareUserEmail(signup.Email, app.keys.EmailKey),
Created: time.Now().Truncate(time.Second).UTC(),
}
// Create actual user
if err := app.db.CreateUser(app.cfg, u, desiredUsername); err != nil {
if err := app.db.CreateUser(app.cfg, u, desiredUsername, signup.Description); err != nil {
return nil, err
}
// Log invite if needed
if signup.InviteCode != "" {
cu, err := app.db.GetUserForAuth(signup.Alias)
if err != nil {
return nil, err
}
err = app.db.CreateInvitedUser(signup.InviteCode, cu.ID)
err = app.db.CreateInvitedUser(signup.InviteCode, u.ID)
if err != nil {
return nil, err
}
@ -186,20 +189,35 @@ func signupWithRegistration(app *App, signup userRegistration, w http.ResponseWr
resUser := &AuthUser{
User: u,
}
if !createdWithPass {
resUser.Password = signup.Pass
}
title := signup.Alias
if signup.Normalize {
title = desiredUsername
}
resUser.Collections = &[]Collection{
{
Alias: signup.Alias,
Title: title,
Alias: signup.Alias,
Title: title,
Description: signup.Description,
},
}
var coll *Collection
if signup.Monetization != "" {
if coll == nil {
coll, err = app.db.GetCollection(signup.Alias)
if err != nil {
log.Error("Unable to get new collection '%s' for monetization on signup: %v", signup.Alias, err)
return nil, err
}
}
err = app.db.SetCollectionAttribute(coll.ID, "monetization_pointer", signup.Monetization)
if err != nil {
log.Error("Unable to add monetization on signup: %v", err)
return nil, err
}
coll.Monetization = signup.Monetization
}
var token string
if reqJSON && !signup.Web {
token, err = app.db.GetAccessToken(u.ID)
@ -303,24 +321,20 @@ func viewLogin(app *App, w http.ResponseWriter, r *http.Request) error {
p := &struct {
page.StaticPage
To string
Message template.HTML
Flashes []template.HTML
LoginUsername string
OauthSlack bool
OauthWriteAs bool
OauthGitlab bool
GitlabDisplayName string
*OAuthButtons
To string
Message template.HTML
Flashes []template.HTML
EmailEnabled bool
LoginUsername string
}{
pageForReq(app, r),
r.FormValue("to"),
template.HTML(""),
[]template.HTML{},
getTempInfo(app, "login-user", r, w),
app.Config().SlackOauth.ClientID != "",
app.Config().WriteAsOauth.ClientID != "",
app.Config().GitlabOauth.ClientID != "",
config.OrDefaultString(app.Config().GitlabOauth.DisplayName, gitlabDisplayName),
StaticPage: pageForReq(app, r),
OAuthButtons: NewOAuthButtons(app.Config()),
To: r.FormValue("to"),
Message: template.HTML(""),
Flashes: []template.HTML{},
EmailEnabled: app.cfg.Email.Enabled(),
LoginUsername: getTempInfo(app, "login-user", r, w),
}
if earlyError != "" {
@ -395,6 +409,11 @@ func login(app *App, w http.ResponseWriter, r *http.Request) error {
var err error
var signin userCredentials
if app.cfg.App.DisablePasswordAuth {
err := ErrDisabledPasswordAuth
return err
}
// Log in with one-time token if one is given
if oneTimeToken != "" {
log.Info("Login: Logging user in via token.")
@ -489,10 +508,13 @@ func login(app *App, w http.ResponseWriter, r *http.Request) error {
// User has no email set, so check if they haven't added a password, either,
// so we can return a more helpful error message.
if hasPass, _ := app.db.IsUserPassSet(u.ID); !hasPass {
log.Info("Tried logging in to %s, but no password or email.", signin.Alias)
log.Info("Tried logging into %s, but no password or email.", signin.Alias)
return impart.HTTPError{http.StatusPreconditionFailed, "This user never added a password or email address. Please contact us for help."}
}
}
if len(u.HashedPass) == 0 {
return impart.HTTPError{http.StatusUnauthorized, "This user never set a password. Perhaps try logging in via OAuth?"}
}
if !auth.Authenticated(u.HashedPass, []byte(signin.Pass)) {
return impart.HTTPError{http.StatusUnauthorized, "Incorrect password."}
}
@ -559,7 +581,7 @@ func getVerboseAuthUser(app *App, token string, u *User, verbose bool) *AuthUser
}
passIsSet, err := app.db.IsUserPassSet(u.ID)
if err != nil {
// TODO: correct error meesage
// TODO: correct error message
log.Error("Login: Unable to get user collections: %v", err)
}
@ -692,6 +714,22 @@ func viewMyPostsAPI(app *App, u *User, w http.ResponseWriter, r *http.Request) e
return ErrBadRequestedType
}
isAnonPosts := r.FormValue("anonymous") == "1"
if isAnonPosts {
pageStr := r.FormValue("page")
pg, err := strconv.Atoi(pageStr)
if err != nil {
log.Error("Error parsing page parameter '%s': %s", pageStr, err)
pg = 1
}
p, err := app.db.GetAnonymousPosts(u, pg)
if err != nil {
return err
}
return impart.WriteSuccess(w, p, http.StatusOK)
}
var err error
p := GetPostsCache(u.ID)
if p == nil {
@ -732,7 +770,7 @@ func viewMyCollectionsAPI(app *App, u *User, w http.ResponseWriter, r *http.Requ
}
func viewArticles(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
p, err := app.db.GetAnonymousPosts(u)
p, err := app.db.GetAnonymousPosts(u, 1)
if err != nil {
log.Error("unable to fetch anon posts: %v", err)
}
@ -753,6 +791,9 @@ func viewArticles(app *App, u *User, w http.ResponseWriter, r *http.Request) err
silenced, err := app.db.IsUserSilenced(u.ID)
if err != nil {
if err == ErrUserNotFound {
return err
}
log.Error("view articles: %v", err)
}
d := struct {
@ -788,7 +829,10 @@ func viewCollections(app *App, u *User, w http.ResponseWriter, r *http.Request)
silenced, err := app.db.IsUserSilenced(u.ID)
if err != nil {
log.Error("view collections %v", err)
if err == ErrUserNotFound {
return err
}
log.Error("view collections: %v", err)
return fmt.Errorf("view collections: %v", err)
}
d := struct {
@ -824,6 +868,9 @@ func viewEditCollection(app *App, u *User, w http.ResponseWriter, r *http.Reques
silenced, err := app.db.IsUserSilenced(u.ID)
if err != nil {
if err == ErrUserNotFound {
return err
}
log.Error("view edit collection %v", err)
return fmt.Errorf("view edit collection: %v", err)
}
@ -832,10 +879,18 @@ func viewEditCollection(app *App, u *User, w http.ResponseWriter, r *http.Reques
*UserPage
*Collection
Silenced bool
config.EmailCfg
LetterReplyTo string
}{
UserPage: NewUserPage(app, r, u, "Edit "+c.DisplayTitle(), flashes),
Collection: c,
Silenced: silenced,
EmailCfg: app.cfg.Email,
}
obj.UserPage.CollAlias = c.Alias
if obj.EmailCfg.Enabled() {
obj.LetterReplyTo = app.db.GetCollectionAttribute(c.ID, collAttrLetterReplyTo)
}
showUserPage(w, "collection", obj)
@ -983,9 +1038,10 @@ func viewStats(app *App, u *User, w http.ResponseWriter, r *http.Request) error
if c.OwnerID != u.ID {
return ErrCollectionNotFound
}
c.hostName = app.cfg.App.Host
}
topPosts, err := app.db.GetTopPosts(u, alias)
topPosts, err := app.db.GetTopPosts(u, alias, c.hostName)
if err != nil {
log.Error("Unable to get top posts: %v", err)
return err
@ -999,23 +1055,30 @@ func viewStats(app *App, u *User, w http.ResponseWriter, r *http.Request) error
silenced, err := app.db.IsUserSilenced(u.ID)
if err != nil {
if err == ErrUserNotFound {
return err
}
log.Error("view stats: %v", err)
return err
}
obj := struct {
*UserPage
VisitsBlog string
Collection *Collection
TopPosts *[]PublicPost
APFollowers int
Silenced bool
VisitsBlog string
Collection *Collection
TopPosts *[]PublicPost
APFollowers int
EmailEnabled bool
EmailSubscribers int
Silenced bool
}{
UserPage: NewUserPage(app, r, u, titleStats+"Stats", flashes),
VisitsBlog: alias,
Collection: c,
TopPosts: topPosts,
Silenced: silenced,
UserPage: NewUserPage(app, r, u, titleStats+"Stats", flashes),
VisitsBlog: alias,
Collection: c,
TopPosts: topPosts,
EmailEnabled: app.cfg.Email.Enabled(),
Silenced: silenced,
}
obj.UserPage.CollAlias = c.Alias
if app.cfg.App.Federation {
folls, err := app.db.GetAPFollowers(c)
if err != nil {
@ -1023,14 +1086,79 @@ func viewStats(app *App, u *User, w http.ResponseWriter, r *http.Request) error
}
obj.APFollowers = len(*folls)
}
if obj.EmailEnabled {
subs, err := app.db.GetEmailSubscribers(c.ID, true)
if err != nil {
return err
}
obj.EmailSubscribers = len(subs)
}
showUserPage(w, "stats", obj)
return nil
}
func handleViewSubscribers(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
c, err := app.db.GetCollection(vars["collection"])
if err != nil {
return err
}
filter := r.FormValue("filter")
flashes, _ := getSessionFlashes(app, w, r, nil)
obj := struct {
*UserPage
Collection CollectionNav
EmailSubs []*EmailSubscriber
Followers *[]RemoteUser
Silenced bool
Filter string
FederationEnabled bool
CanEmailSub bool
CanAddSubs bool
EmailSubsEnabled bool
}{
UserPage: NewUserPage(app, r, u, c.DisplayTitle()+" Subscribers", flashes),
Collection: CollectionNav{
Collection: c,
Path: r.URL.Path,
SingleUser: app.cfg.App.SingleUser,
},
Silenced: u.IsSilenced(),
Filter: filter,
FederationEnabled: app.cfg.App.Federation,
CanEmailSub: app.cfg.Email.Enabled(),
EmailSubsEnabled: c.EmailSubsEnabled(),
}
obj.Followers, err = app.db.GetAPFollowers(c)
if err != nil {
return err
}
obj.EmailSubs, err = app.db.GetEmailSubscribers(c.ID, true)
if err != nil {
return err
}
if obj.Filter == "" {
// Set permission to add email subscribers
//obj.CanAddSubs = app.db.GetUserAttribute(c.OwnerID, userAttrCanAddEmailSubs) == "1"
}
showUserPage(w, "subscribers", obj)
return nil
}
func viewSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
fullUser, err := app.db.GetUserByID(u.ID)
if err != nil {
if err == ErrUserNotFound {
return err
}
log.Error("Unable to get user for settings: %s", err)
return impart.HTTPError{http.StatusInternalServerError, "Unable to retrieve user data. The humans have been alerted."}
}
@ -1046,13 +1174,15 @@ func viewSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) err
enableOauthSlack := app.Config().SlackOauth.ClientID != ""
enableOauthWriteAs := app.Config().WriteAsOauth.ClientID != ""
enableOauthGitLab := app.Config().GitlabOauth.ClientID != ""
enableOauthGeneric := app.Config().GenericOauth.ClientID != ""
enableOauthGitea := app.Config().GiteaOauth.ClientID != ""
oauthAccounts, err := app.db.GetOauthAccounts(r.Context(), u.ID)
if err != nil {
log.Error("Unable to get oauth accounts for settings: %s", err)
return impart.HTTPError{http.StatusInternalServerError, "Unable to retrieve user data. The humans have been alerted."}
}
for _, oauthAccount := range oauthAccounts {
for idx, oauthAccount := range oauthAccounts {
switch oauthAccount.Provider {
case "slack":
enableOauthSlack = false
@ -1060,41 +1190,262 @@ func viewSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) err
enableOauthWriteAs = false
case "gitlab":
enableOauthGitLab = false
case "generic":
oauthAccounts[idx].DisplayName = app.Config().GenericOauth.DisplayName
oauthAccounts[idx].AllowDisconnect = app.Config().GenericOauth.AllowDisconnect
enableOauthGeneric = false
case "gitea":
enableOauthGitea = false
}
}
displayOauthSection := enableOauthSlack || enableOauthWriteAs || enableOauthGitLab || len(oauthAccounts) > 0
displayOauthSection := enableOauthSlack || enableOauthWriteAs || enableOauthGitLab || enableOauthGeneric || enableOauthGitea || len(oauthAccounts) > 0
obj := struct {
*UserPage
Email string
HasPass bool
IsLogOut bool
Silenced bool
OauthSection bool
OauthAccounts []oauthAccountInfo
OauthSlack bool
OauthWriteAs bool
OauthGitLab bool
GitLabDisplayName string
Email string
HasPass bool
IsLogOut bool
Silenced bool
CSRFField template.HTML
OauthSection bool
OauthAccounts []oauthAccountInfo
OauthSlack bool
OauthWriteAs bool
OauthGitLab bool
GitLabDisplayName string
OauthGeneric bool
OauthGenericDisplayName string
OauthGitea bool
GiteaDisplayName string
}{
UserPage: NewUserPage(app, r, u, "Account Settings", flashes),
Email: fullUser.EmailClear(app.keys),
HasPass: passIsSet,
IsLogOut: r.FormValue("logout") == "1",
Silenced: fullUser.IsSilenced(),
OauthSection: displayOauthSection,
OauthAccounts: oauthAccounts,
OauthSlack: enableOauthSlack,
OauthWriteAs: enableOauthWriteAs,
OauthGitLab: enableOauthGitLab,
GitLabDisplayName: config.OrDefaultString(app.Config().GitlabOauth.DisplayName, gitlabDisplayName),
UserPage: NewUserPage(app, r, u, "Account Settings", flashes),
Email: fullUser.EmailClear(app.keys),
HasPass: passIsSet,
IsLogOut: r.FormValue("logout") == "1",
Silenced: fullUser.IsSilenced(),
CSRFField: csrf.TemplateField(r),
OauthSection: displayOauthSection,
OauthAccounts: oauthAccounts,
OauthSlack: enableOauthSlack,
OauthWriteAs: enableOauthWriteAs,
OauthGitLab: enableOauthGitLab,
GitLabDisplayName: config.OrDefaultString(app.Config().GitlabOauth.DisplayName, gitlabDisplayName),
OauthGeneric: enableOauthGeneric,
OauthGenericDisplayName: config.OrDefaultString(app.Config().GenericOauth.DisplayName, genericOauthDisplayName),
OauthGitea: enableOauthGitea,
GiteaDisplayName: config.OrDefaultString(app.Config().GiteaOauth.DisplayName, giteaDisplayName),
}
showUserPage(w, "settings", obj)
return nil
}
func viewResetPassword(app *App, w http.ResponseWriter, r *http.Request) error {
token := r.FormValue("t")
resetting := false
var userID int64 = 0
if token != "" {
// Show new password page
userID = app.db.GetUserFromPasswordReset(token)
if userID == 0 {
return impart.HTTPError{http.StatusNotFound, ""}
}
resetting = true
}
if r.Method == http.MethodPost {
newPass := r.FormValue("new-pass")
if newPass == "" {
// Send password reset email
return handleResetPasswordInit(app, w, r)
}
// Do actual password reset
// Assumes token has been validated above
err := doAutomatedPasswordChange(app, userID, newPass)
if err != nil {
return err
}
err = app.db.ConsumePasswordResetToken(token)
if err != nil {
log.Error("Couldn't consume token %s for user %d!!! %s", token, userID, err)
}
addSessionFlash(app, w, r, "Your password was reset. Now you can log in below.", nil)
return impart.HTTPError{http.StatusFound, "/login"}
}
f, _ := getSessionFlashes(app, w, r, nil)
// Show reset password page
d := struct {
page.StaticPage
Flashes []string
EmailEnabled bool
CSRFField template.HTML
Token string
IsResetting bool
IsSent bool
}{
StaticPage: pageForReq(app, r),
Flashes: f,
EmailEnabled: app.cfg.Email.Enabled(),
CSRFField: csrf.TemplateField(r),
Token: token,
IsResetting: resetting,
IsSent: r.FormValue("sent") == "1",
}
err := pages["reset.tmpl"].ExecuteTemplate(w, "base", d)
if err != nil {
log.Error("Unable to render password reset page: %v", err)
return err
}
return err
}
func doAutomatedPasswordChange(app *App, userID int64, newPass string) error {
// Do password reset
hashedPass, err := auth.HashPass([]byte(newPass))
if err != nil {
return impart.HTTPError{http.StatusInternalServerError, "Could not create password hash."}
}
// Do update
err = app.db.ChangePassphrase(userID, true, "", hashedPass)
if err != nil {
return err
}
return nil
}
func handleResetPasswordInit(app *App, w http.ResponseWriter, r *http.Request) error {
returnLoc := impart.HTTPError{http.StatusFound, "/reset"}
if !app.cfg.Email.Enabled() {
// Email isn't configured, so there's nothing to do; send back to the reset form, where they'll get an explanation
return returnLoc
}
ip := spam.GetIP(r)
alias := r.FormValue("alias")
u, err := app.db.GetUserForAuth(alias)
if err != nil {
if strings.IndexAny(alias, "@") > 0 {
addSessionFlash(app, w, r, ErrUserNotFoundEmail.Message, nil)
return returnLoc
}
addSessionFlash(app, w, r, ErrUserNotFound.Message, nil)
return returnLoc
}
if u.IsAdmin() {
// Prevent any reset emails on admin accounts
log.Error("Admin reset attempt", `Someone just tried to reset the password for an admin (ID %d - %s). IP address: %s`, u.ID, u.Username, ip)
return returnLoc
}
if u.Email.String == "" {
err := impart.HTTPError{http.StatusPreconditionFailed, "User doesn't have an email address. Please contact us (" + app.cfg.App.Host + "/contact) to reset your password."}
addSessionFlash(app, w, r, err.Message, nil)
return returnLoc
}
if isSet, _ := app.db.IsUserPassSet(u.ID); !isSet {
err = loginViaEmail(app, u.Username, "/me/settings")
if err != nil {
return err
}
addSessionFlash(app, w, r, "We've emailed you a link to log in with.", nil)
return returnLoc
}
token, err := app.db.CreatePasswordResetToken(u.ID)
if err != nil {
log.Error("Error resetting password: %s", err)
addSessionFlash(app, w, r, ErrInternalGeneral.Message, nil)
return returnLoc
}
err = emailPasswordReset(app, u.EmailClear(app.keys), token)
if err != nil {
log.Error("Error emailing password reset: %s", err)
addSessionFlash(app, w, r, ErrInternalGeneral.Message, nil)
return returnLoc
}
addSessionFlash(app, w, r, "We sent an email to the address associated with this account.", nil)
returnLoc.Message += "?sent=1"
return returnLoc
}
func emailPasswordReset(app *App, toEmail, token string) error {
// Send email
gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate)
footerPara := "Didn't request this password reset? Your account is still safe, and you can safely ignore this email."
plainMsg := fmt.Sprintf("We received a request to reset your password on %s. Please click the following link to continue (or copy and paste it into your browser): %s/reset?t=%s\n\n%s", app.cfg.App.SiteName, app.cfg.App.Host, token, footerPara)
m := mailgun.NewMessage(app.cfg.App.SiteName+" <noreply-password@"+app.cfg.Email.Domain+">", "Reset Your "+app.cfg.App.SiteName+" Password", plainMsg, fmt.Sprintf("<%s>", toEmail))
m.AddTag("Password Reset")
m.SetHtml(fmt.Sprintf(`<html>
<body style="font-family:Lora, 'Palatino Linotype', Palatino, Baskerville, 'Book Antiqua', 'New York', 'DejaVu serif', serif; font-size: 100%%; margin:1em 2em;">
<div style="margin:0 auto; max-width: 40em; font-size: 1.2em;">
<h1 style="font-size:1.75em"><a style="text-decoration:none;color:#000;" href="%s">%s</a></h1>
<p>We received a request to reset your password on %s. Please click the following link to continue:</p>
<p style="font-size:1.2em;margin-bottom:1.5em;"><a href="%s/reset?t=%s">Reset your password</a></p>
<p style="font-size: 0.86em;margin:1em auto">%s</p>
</div>
</body>
</html>`, app.cfg.App.Host, app.cfg.App.SiteName, app.cfg.App.SiteName, app.cfg.App.Host, token, footerPara))
_, _, err := gun.Send(m)
return err
}
func loginViaEmail(app *App, alias, redirectTo string) error {
if !app.cfg.Email.Enabled() {
return fmt.Errorf("EMAIL ISN'T CONFIGURED on this server")
}
// Make sure user has added an email
// TODO: create a new func to just get user's email; "ForAuth" doesn't match here
u, _ := app.db.GetUserForAuth(alias)
if u == nil {
if strings.IndexAny(alias, "@") > 0 {
return ErrUserNotFoundEmail
}
return ErrUserNotFound
}
if u.Email.String == "" {
return impart.HTTPError{http.StatusPreconditionFailed, "User doesn't have an email address. Log in with password, instead."}
}
// Generate one-time login token
t, err := app.db.GetTemporaryOneTimeAccessToken(u.ID, 60*15, true)
if err != nil {
log.Error("Unable to generate token for email login: %s", err)
return impart.HTTPError{http.StatusInternalServerError, "Unable to generate token."}
}
// Send email
gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate)
toEmail := u.EmailClear(app.keys)
footerPara := "This link will only work once and expires in 15 minutes. Didn't ask us to log in? You can safely ignore this email."
plainMsg := fmt.Sprintf("Log in to %s here: %s/login?to=%s&with=%s\n\n%s", app.cfg.App.SiteName, app.cfg.App.Host, redirectTo, t, footerPara)
m := mailgun.NewMessage(app.cfg.App.SiteName+" <noreply-login@"+app.cfg.Email.Domain+">", "Log in to "+app.cfg.App.SiteName, plainMsg, fmt.Sprintf("<%s>", toEmail))
m.AddTag("Email Login")
m.SetHtml(fmt.Sprintf(`<html>
<body style="font-family:Lora, 'Palatino Linotype', Palatino, Baskerville, 'Book Antiqua', 'New York', 'DejaVu serif', serif; font-size: 100%%; margin:1em 2em;">
<div style="margin:0 auto; max-width: 40em; font-size: 1.2em;">
<h1 style="font-size:1.75em"><a style="text-decoration:none;color:#000;" href="%s">%s</a></h1>
<p style="font-size:1.2em;margin-bottom:1.5em;text-align:center"><a href="%s/login?to=%s&with=%s">Log in to %s here</a>.</p>
<p style="font-size: 0.86em;color:#666;text-align:center;max-width:35em;margin:1em auto">%s</p>
</div>
</body>
</html>`, app.cfg.App.Host, app.cfg.App.SiteName, app.cfg.App.Host, redirectTo, t, app.cfg.App.SiteName, footerPara))
_, _, err = gun.Send(m)
return err
}
func saveTempInfo(app *App, key, val string, r *http.Request, w http.ResponseWriter) error {
session, err := app.sessionStore.Get(r, "t")
if err != nil {
@ -1133,6 +1484,32 @@ func getTempInfo(app *App, key string, r *http.Request, w http.ResponseWriter) s
return s
}
func handleUserDelete(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
if !app.cfg.App.OpenDeletion {
return impart.HTTPError{http.StatusForbidden, "Open account deletion is disabled on this instance."}
}
confirmUsername := r.PostFormValue("confirm-username")
if u.Username != confirmUsername {
return impart.HTTPError{http.StatusBadRequest, "Confirmation username must match your username exactly."}
}
// Check for account deletion safeguards in place
if u.IsAdmin() {
return impart.HTTPError{http.StatusForbidden, "Cannot delete admin."}
}
err := app.db.DeleteAccount(u.ID)
if err != nil {
log.Error("user delete account: %v", err)
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not delete account: %v", err)}
}
// FIXME: This doesn't ever appear to the user, as (I believe) the value is erased when the session cookie is reset
_ = addSessionFlash(app, w, r, "Thanks for writing with us! You account was deleted successfully.", nil)
return impart.HTTPError{http.StatusFound, "/me/logout"}
}
func removeOauth(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
provider := r.FormValue("provider")
clientID := r.FormValue("client_id")
@ -1154,6 +1531,7 @@ func prepareUserEmail(input string, emailKey []byte) zero.String {
log.Error("Unable to encrypt email: %s\n", err)
} else {
email.String = string(encEmail)
}
}
return email

View File

@ -5,7 +5,6 @@ import (
"fmt"
"html/template"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
@ -100,7 +99,7 @@ func handleImport(app *App, u *User, w http.ResponseWriter, r *http.Request) err
}
defer file.Close()
tempFile, err := ioutil.TempFile("", "post-upload-*.txt")
tempFile, err := os.CreateTemp("", "post-upload-*.txt")
if err != nil {
fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
log.Error("import file: create temp file %s: %v", formFile.Filename, err)

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018-2020 A Bunch Tell LLC.
* Copyright © 2018-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -17,21 +17,25 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/http/httputil"
"net/url"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gorilla/mux"
"github.com/writeas/activity/streams"
"github.com/writeas/activityserve"
"github.com/writeas/httpsig"
"github.com/writeas/impart"
"github.com/writeas/nerds/store"
"github.com/writeas/web-core/activitypub"
"github.com/writeas/web-core/activitystreams"
"github.com/writeas/web-core/id"
"github.com/writeas/web-core/log"
"github.com/writeas/web-core/silobridge"
)
const (
@ -41,12 +45,40 @@ const (
apCacheTime = time.Minute
)
var instanceColl *Collection
func initActivityPub(app *App) {
ur, _ := url.Parse(app.cfg.App.Host)
instanceColl = &Collection{
ID: 0,
Alias: ur.Host,
Title: ur.Host,
db: app.db,
hostName: app.cfg.App.Host,
}
}
type RemoteUser struct {
ID int64
ActorID string
Inbox string
SharedInbox string
URL string
Handle string
Created time.Time
}
func (ru *RemoteUser) CreatedFriendly() string {
return ru.Created.Format("January 2, 2006")
}
func (ru *RemoteUser) EstimatedHandle() string {
if ru.Handle != "" {
return ru.Handle
}
username := filepath.Base(ru.ActorID)
host, _ := url.Parse(ru.ActorID)
return username + "@" + host.Host
}
func (ru *RemoteUser) AsPerson() *activitystreams.Person {
@ -76,12 +108,17 @@ func handleFetchCollectionActivities(app *App, w http.ResponseWriter, r *http.Re
vars := mux.Vars(r)
alias := vars["alias"]
if alias == "" {
alias = filepath.Base(r.RequestURI)
}
// TODO: enforce visibility
// Get base Collection data
var c *Collection
var err error
if app.cfg.App.SingleUser {
if alias == r.Host {
c = instanceColl
} else if app.cfg.App.SingleUser {
c, err = app.db.GetCollectionByID(1)
} else {
c, err = app.db.GetCollection(alias)
@ -89,16 +126,19 @@ func handleFetchCollectionActivities(app *App, w http.ResponseWriter, r *http.Re
if err != nil {
return err
}
silenced, err := app.db.IsUserSilenced(c.OwnerID)
if err != nil {
log.Error("fetch collection activities: %v", err)
return ErrInternalGeneral
}
if silenced {
return ErrCollectionNotFound
}
c.hostName = app.cfg.App.Host
if !c.IsInstanceColl() {
silenced, err := app.db.IsUserSilenced(c.OwnerID)
if err != nil {
log.Error("fetch collection activities: %v", err)
return ErrInternalGeneral
}
if silenced {
return ErrCollectionNotFound
}
}
p := c.PersonObject()
setCacheControl(w, apCacheTime)
@ -160,6 +200,7 @@ func handleFetchCollectionOutbox(app *App, w http.ResponseWriter, r *http.Reques
pp.Collection = res
o := pp.ActivityObject(app)
a := activitystreams.NewCreateActivity(o)
a.Context = nil
ocp.OrderedItems = append(ocp.OrderedItems, *a)
}
@ -330,7 +371,7 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request
if followID == nil {
log.Error("Didn't resolve follow ID")
} else {
aID := c.FederatedAccount() + "#accept-" + store.GenerateFriendlyRandomString(20)
aID := c.FederatedAccount() + "#accept-" + id.GenerateFriendlyRandomString(20)
acceptID, err := url.Parse(aID)
if err != nil {
log.Error("Couldn't parse generated Accept URL '%s': %v", aID, err)
@ -396,7 +437,9 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request
go func() {
if to == nil {
log.Error("No to! %v", err)
if debugging {
log.Error("No `to` value!")
}
return
}
@ -427,7 +470,7 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request
followerID = remoteUser.ID
} else {
// Add follower locally, since it wasn't found before
res, err := t.Exec("INSERT INTO remoteusers (actor_id, inbox, shared_inbox) VALUES (?, ?, ?)", fullActor.ID, fullActor.Inbox, fullActor.Endpoints.SharedInbox)
res, err := t.Exec("INSERT INTO remoteusers (actor_id, inbox, shared_inbox, url) VALUES (?, ?, ?, ?)", fullActor.ID, fullActor.Inbox, fullActor.Endpoints.SharedInbox, fullActor.URL)
if err != nil {
// if duplicate key, res will be nil and panic on
// res.LastInsertId below
@ -491,7 +534,7 @@ func makeActivityPost(hostName string, p *activitystreams.Person, url string, m
r, _ := http.NewRequest("POST", url, bytes.NewBuffer(b))
r.Header.Add("Content-Type", "application/activity+json")
r.Header.Set("User-Agent", "Go ("+serverSoftware+"/"+softwareVer+"; +"+hostName+")")
r.Header.Set("User-Agent", ServerUserAgent(hostName))
h := sha256.New()
h.Write(b)
r.Header.Add("Digest", "SHA-256="+base64.StdEncoding.EncodeToString(h.Sum(nil)))
@ -524,7 +567,7 @@ func makeActivityPost(hostName string, p *activitystreams.Person, url string, m
defer resp.Body.Close()
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
@ -541,7 +584,23 @@ func resolveIRI(hostName, url string) ([]byte, error) {
r, _ := http.NewRequest("GET", url, nil)
r.Header.Add("Accept", "application/activity+json")
r.Header.Set("User-Agent", "Go ("+serverSoftware+"/"+softwareVer+"; +"+hostName+")")
r.Header.Set("User-Agent", ServerUserAgent(hostName))
p := instanceColl.PersonObject()
h := sha256.New()
h.Write([]byte{})
r.Header.Add("Digest", "SHA-256="+base64.StdEncoding.EncodeToString(h.Sum(nil)))
// Sign using the 'Signature' header
privKey, err := activitypub.DecodePrivateKey(p.GetPrivKey())
if err != nil {
return nil, err
}
signer := httpsig.NewSigner(p.PublicKey.ID, privKey, httpsig.RSASHA256, []string{"(request-target)", "date", "host", "digest"})
err = signer.SignSigHeader(r)
if err != nil {
log.Error("Can't sign: %v", err)
}
if debugging {
dump, err := httputil.DumpRequestOut(r, true)
@ -560,7 +619,7 @@ func resolveIRI(hostName, url string) ([]byte, error) {
defer resp.Body.Close()
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -603,10 +662,7 @@ func deleteFederatedPost(app *App, p *PublicPost, collID int64) error {
for si, instFolls := range inboxes {
na.CC = []string{}
for _, f := range instFolls {
na.CC = append(na.CC, f)
}
na.CC = append(na.CC, instFolls...)
da := activitystreams.NewDeleteActivity(na)
// Make the ID unique to ensure it works in Pleroma
// See: https://git.pleroma.social/pleroma/pleroma/issues/1481
@ -621,6 +677,16 @@ func deleteFederatedPost(app *App, p *PublicPost, collID int64) error {
}
func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error {
// If app is private, do not federate
if app.cfg.App.Private {
return nil
}
// Do not federate posts from private or protected blogs
if p.Collection.Visibility == CollPrivate || p.Collection.Visibility == CollProtected {
return nil
}
if debugging {
if isUpdate {
log.Info("Federating updated post!")
@ -628,6 +694,7 @@ func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error {
log.Info("Federating new post!")
}
}
actor := p.Collection.PersonObject(collID)
na := p.ActivityObject(app)
@ -661,12 +728,11 @@ func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error {
// add all followers from that instance
// to the CC field
na.CC = []string{}
for _, f := range instFolls {
na.CC = append(na.CC, f)
}
na.CC = append(na.CC, instFolls...)
// create a new "Create" activity
// with our article as object
if isUpdate {
na.Updated = &p.Updated
activity = activitystreams.NewUpdateActivity(na)
} else {
activity = activitystreams.NewCreateActivity(na)
@ -696,6 +762,10 @@ func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error {
// I don't believe we'd ever have too many mentions in a single post that this
// could become a burden.
remoteUser, err := getRemoteUser(app, tag.HRef)
if err != nil {
log.Error("Unable to find remote user %s. Skipping: %v", tag.HRef, err)
continue
}
err = makeActivityPost(app.cfg.App.Host, actor, remoteUser.Inbox, activity)
if err != nil {
log.Error("Couldn't post! %v", err)
@ -708,8 +778,8 @@ func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error {
func getRemoteUser(app *App, actorID string) (*RemoteUser, error) {
u := RemoteUser{ActorID: actorID}
var handle sql.NullString
err := app.db.QueryRow("SELECT id, inbox, shared_inbox, handle FROM remoteusers WHERE actor_id = ?", actorID).Scan(&u.ID, &u.Inbox, &u.SharedInbox, &handle)
var urlVal, handle sql.NullString
err := app.db.QueryRow("SELECT id, inbox, shared_inbox, url, handle FROM remoteusers WHERE actor_id = ?", actorID).Scan(&u.ID, &u.Inbox, &u.SharedInbox, &urlVal, &handle)
switch {
case err == sql.ErrNoRows:
return nil, impart.HTTPError{http.StatusNotFound, "No remote user with that ID."}
@ -718,6 +788,7 @@ func getRemoteUser(app *App, actorID string) (*RemoteUser, error) {
return nil, err
}
u.URL = urlVal.String
u.Handle = handle.String
return &u, nil
@ -727,7 +798,8 @@ func getRemoteUser(app *App, actorID string) (*RemoteUser, error) {
// from the @user@server.tld handle
func getRemoteUserFromHandle(app *App, handle string) (*RemoteUser, error) {
u := RemoteUser{Handle: handle}
err := app.db.QueryRow("SELECT id, actor_id, inbox, shared_inbox FROM remoteusers WHERE handle = ?", handle).Scan(&u.ID, &u.ActorID, &u.Inbox, &u.SharedInbox)
var urlVal sql.NullString
err := app.db.QueryRow("SELECT id, actor_id, inbox, shared_inbox, url FROM remoteusers WHERE handle = ?", handle).Scan(&u.ID, &u.ActorID, &u.Inbox, &u.SharedInbox, &urlVal)
switch {
case err == sql.ErrNoRows:
return nil, ErrRemoteUserNotFound
@ -735,6 +807,7 @@ func getRemoteUserFromHandle(app *App, handle string) (*RemoteUser, error) {
log.Error("Couldn't get remote user %s: %v", handle, err)
return nil, err
}
u.URL = urlVal.String
return &u, nil
}
@ -768,6 +841,69 @@ func getActor(app *App, actorIRI string) (*activitystreams.Person, *RemoteUser,
return actor, remoteUser, nil
}
func GetProfileURLFromHandle(app *App, handle string) (string, error) {
handle = strings.TrimLeft(handle, "@")
actorIRI := ""
parts := strings.Split(handle, "@")
if len(parts) != 2 {
return "", fmt.Errorf("invalid handle format")
}
domain := parts[1]
// Check non-AP instances
if siloProfileURL := silobridge.Profile(parts[0], domain); siloProfileURL != "" {
return siloProfileURL, nil
}
remoteUser, err := getRemoteUserFromHandle(app, handle)
if err != nil {
// can't find using handle in the table but the table may already have this user without
// handle from a previous version
// TODO: Make this determination. We should know whether a user exists without a handle, or doesn't exist at all
actorIRI = RemoteLookup(handle)
_, errRemoteUser := getRemoteUser(app, actorIRI)
// if it exists then we need to update the handle
if errRemoteUser == nil {
_, err := app.db.Exec("UPDATE remoteusers SET handle = ? WHERE actor_id = ?", handle, actorIRI)
if err != nil {
log.Error("Couldn't update handle '%s' for user %s", handle, actorIRI)
}
} else {
// this probably means we don't have the user in the table so let's try to insert it
// here we need to ask the server for the inboxes
remoteActor, err := activityserve.NewRemoteActor(actorIRI)
if err != nil {
log.Error("Couldn't fetch remote actor: %v", err)
}
if debugging {
log.Info("Got remote actor: %s %s %s %s %s", actorIRI, remoteActor.GetInbox(), remoteActor.GetSharedInbox(), remoteActor.URL(), handle)
}
_, err = app.db.Exec("INSERT INTO remoteusers (actor_id, inbox, shared_inbox, url, handle) VALUES(?, ?, ?, ?, ?)", actorIRI, remoteActor.GetInbox(), remoteActor.GetSharedInbox(), remoteActor.URL(), handle)
if err != nil {
log.Error("Couldn't insert remote user: %v", err)
return "", err
}
actorIRI = remoteActor.URL()
}
} else if remoteUser.URL == "" {
log.Info("Remote user %s URL empty, fetching", remoteUser.ActorID)
newRemoteActor, err := activityserve.NewRemoteActor(remoteUser.ActorID)
if err != nil {
log.Error("Couldn't fetch remote actor: %v", err)
} else {
_, err := app.db.Exec("UPDATE remoteusers SET url = ? WHERE actor_id = ?", newRemoteActor.URL(), remoteUser.ActorID)
if err != nil {
log.Error("Couldn't update handle '%s' for user %s", handle, actorIRI)
} else {
actorIRI = newRemoteActor.URL()
}
}
} else {
actorIRI = remoteUser.URL
}
return actorIRI, nil
}
// unmarshal actor normalizes the actor response to conform to
// the type Person from github.com/writeas/web-core/activitysteams
//

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018-2020 A Bunch Tell LLC.
* Copyright © 2018-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -13,6 +13,7 @@ package writefreely
import (
"database/sql"
"fmt"
"html/template"
"net/http"
"runtime"
"strconv"
@ -24,8 +25,8 @@ import (
"github.com/writeas/web-core/auth"
"github.com/writeas/web-core/log"
"github.com/writeas/web-core/passgen"
"github.com/writeas/writefreely/appstats"
"github.com/writeas/writefreely/config"
"github.com/writefreely/writefreely/appstats"
"github.com/writefreely/writefreely/config"
)
var (
@ -102,13 +103,16 @@ func NewAdminPage(app *App) *AdminPage {
return ap
}
func (c instanceContent) UpdatedFriendly() string {
func (c instanceContent) UpdatedFriendly() template.HTML {
/*
// TODO: accept a locale in this method and use that for the format
var loc monday.Locale = monday.LocaleEnUS
return monday.Format(u.Created, monday.DateTimeFormatsByLocale[loc], loc)
*/
return c.Updated.Format("January 2, 2006, 3:04 PM")
if c.Updated.IsZero() {
return "<em>Never</em>"
}
return template.HTML(c.Updated.Format("January 2, 2006, 3:04 PM"))
}
func handleViewAdminDash(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
@ -189,6 +193,7 @@ func handleViewAdminUsers(app *App, u *User, w http.ResponseWriter, r *http.Requ
*AdminPage
Config config.AppCfg
Message string
Flashes []string
Users *[]User
CurPage int
@ -201,6 +206,7 @@ func handleViewAdminUsers(app *App, u *User, w http.ResponseWriter, r *http.Requ
Message: r.FormValue("m"),
}
p.Flashes, _ = getSessionFlashes(app, w, r, nil)
p.TotalUsers = app.db.GetAllUsersCount()
ttlPages := p.TotalUsers / adminUsersPerPage
p.TotalPages = []int{}
@ -312,6 +318,37 @@ func handleViewAdminUser(app *App, u *User, w http.ResponseWriter, r *http.Reque
return nil
}
func handleAdminDeleteUser(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
if !u.IsAdmin() {
return impart.HTTPError{http.StatusForbidden, "Administrator privileges required for this action"}
}
vars := mux.Vars(r)
username := vars["username"]
confirmUsername := r.PostFormValue("confirm-username")
if confirmUsername != username {
return impart.HTTPError{http.StatusBadRequest, "Username was not confirmed"}
}
user, err := app.db.GetUserForAuth(username)
if err == ErrUserNotFound {
return impart.HTTPError{http.StatusNotFound, fmt.Sprintf("User '%s' was not found", username)}
} else if err != nil {
log.Error("get user for deletion: %v", err)
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get user with username '%s': %v", username, err)}
}
err = app.db.DeleteAccount(user.ID)
if err != nil {
log.Error("delete user %s: %v", user.Username, err)
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not delete user account for '%s': %v", username, err)}
}
_ = addSessionFlash(app, w, r, fmt.Sprintf("User \"%s\" was deleted successfully.", username), nil)
return impart.HTTPError{http.StatusFound, "/admin/users"}
}
func handleAdminToggleUserStatus(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
username := vars["username"]
@ -328,6 +365,9 @@ func handleAdminToggleUserStatus(app *App, u *User, w http.ResponseWriter, r *ht
err = app.db.SetUserStatus(user.ID, UserActive)
} else {
err = app.db.SetUserStatus(user.ID, UserSilenced)
// reset the cache to removed silence user posts
updateTimelineCache(app.timeline, true)
}
if err != nil {
log.Error("toggle user silenced: %v", err)
@ -390,9 +430,9 @@ func handleViewAdminPages(app *App, u *User, w http.ResponseWriter, r *http.Requ
}
// Add in default pages
var hasAbout, hasPrivacy bool
var hasAbout, hasContact, hasPrivacy bool
for i, c := range p.Pages {
if hasAbout && hasPrivacy {
if hasAbout && hasContact && hasPrivacy {
break
}
if c.ID == "about" {
@ -400,6 +440,11 @@ func handleViewAdminPages(app *App, u *User, w http.ResponseWriter, r *http.Requ
if !c.Title.Valid {
p.Pages[i].Title = defaultAboutTitle(app.cfg)
}
} else if c.ID == "contact" {
hasContact = true
if !c.Title.Valid {
p.Pages[i].Title = defaultContactTitle()
}
} else if c.ID == "privacy" {
hasPrivacy = true
if !c.Title.Valid {
@ -415,6 +460,13 @@ func handleViewAdminPages(app *App, u *User, w http.ResponseWriter, r *http.Requ
Updated: defaultPageUpdatedTime,
})
}
if !hasContact {
p.Pages = append(p.Pages, &instanceContent{
ID: "contact",
Title: defaultContactTitle(),
Content: defaultContactPage(app),
})
}
if !hasPrivacy {
p.Pages = append(p.Pages, &instanceContent{
ID: "privacy",
@ -453,6 +505,8 @@ func handleViewAdminPage(app *App, u *User, w http.ResponseWriter, r *http.Reque
// Get pre-defined pages, or select slug
if slug == "about" {
p.Content, err = getAboutPage(app)
} else if slug == "contact" {
p.Content, err = getContactPage(app)
} else if slug == "privacy" {
p.Content, err = getPrivacyPage(app)
} else if slug == "landing" {
@ -487,7 +541,7 @@ func handleAdminUpdateSite(app *App, u *User, w http.ResponseWriter, r *http.Req
id := vars["page"]
// Validate
if id != "about" && id != "privacy" && id != "landing" && id != "reader" {
if id != "about" && id != "contact" && id != "privacy" && id != "landing" && id != "reader" {
return impart.HTTPError{http.StatusNotFound, "No such page."}
}
@ -519,6 +573,7 @@ func handleAdminUpdateConfig(apper Apper, u *User, w http.ResponseWriter, r *htt
apper.App().cfg.App.SiteDesc = r.FormValue("site_desc")
apper.App().cfg.App.Landing = r.FormValue("landing")
apper.App().cfg.App.OpenRegistration = r.FormValue("open_registration") == "on"
apper.App().cfg.App.OpenDeletion = r.FormValue("open_deletion") == "on"
mul, err := strconv.Atoi(r.FormValue("min_username_len"))
if err == nil {
apper.App().cfg.App.MinUsernameLen = mul
@ -529,6 +584,7 @@ func handleAdminUpdateConfig(apper Apper, u *User, w http.ResponseWriter, r *htt
}
apper.App().cfg.App.Federation = r.FormValue("federation") == "on"
apper.App().cfg.App.PublicStats = r.FormValue("public_stats") == "on"
apper.App().cfg.App.Monetization = r.FormValue("monetization") == "on"
apper.App().cfg.App.Private = r.FormValue("private") == "on"
apper.App().cfg.App.LocalTimeline = r.FormValue("local_timeline") == "on"
if apper.App().cfg.App.LocalTimeline && apper.App().timeline == nil {

178
app.go
View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018-2019 A Bunch Tell LLC.
* Copyright © 2018-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -13,9 +13,10 @@ package writefreely
import (
"crypto/tls"
"database/sql"
_ "embed"
"fmt"
"html/template"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
@ -30,17 +31,18 @@ import (
"github.com/gorilla/schema"
"github.com/gorilla/sessions"
"github.com/manifoldco/promptui"
stripmd "github.com/writeas/go-strip-markdown"
stripmd "github.com/writeas/go-strip-markdown/v2"
"github.com/writeas/impart"
"github.com/writeas/web-core/auth"
"github.com/writeas/web-core/converter"
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/author"
"github.com/writeas/writefreely/config"
"github.com/writeas/writefreely/key"
"github.com/writeas/writefreely/migrations"
"github.com/writeas/writefreely/page"
"golang.org/x/crypto/acme/autocert"
"github.com/writefreely/writefreely/author"
"github.com/writefreely/writefreely/config"
"github.com/writefreely/writefreely/key"
"github.com/writefreely/writefreely/migrations"
"github.com/writefreely/writefreely/page"
)
const (
@ -56,7 +58,7 @@ var (
debugging bool
// Software version can be set from git env using -ldflags
softwareVer = "0.12.0"
softwareVer = "0.15.0"
// DEPRECATED VARS
isSingleUser bool
@ -166,7 +168,15 @@ func (app *App) LoadKeys() error {
if debugging {
log.Info(" %s", emailKeyPath)
}
app.keys.EmailKey, err = ioutil.ReadFile(emailKeyPath)
executable, err := os.Executable()
if err != nil {
executable = "writefreely"
} else {
executable = filepath.Base(executable)
}
app.keys.EmailKey, err = os.ReadFile(emailKeyPath)
if err != nil {
return err
}
@ -174,7 +184,7 @@ func (app *App) LoadKeys() error {
if debugging {
log.Info(" %s", cookieAuthKeyPath)
}
app.keys.CookieAuthKey, err = ioutil.ReadFile(cookieAuthKeyPath)
app.keys.CookieAuthKey, err = os.ReadFile(cookieAuthKeyPath)
if err != nil {
return err
}
@ -182,11 +192,27 @@ func (app *App) LoadKeys() error {
if debugging {
log.Info(" %s", cookieKeyPath)
}
app.keys.CookieKey, err = ioutil.ReadFile(cookieKeyPath)
app.keys.CookieKey, err = os.ReadFile(cookieKeyPath)
if err != nil {
return err
}
if debugging {
log.Info(" %s", csrfKeyPath)
}
app.keys.CSRFKey, err = os.ReadFile(csrfKeyPath)
if err != nil {
if os.IsNotExist(err) {
log.Error(`Missing key: %s.
Run this command to generate missing keys:
%s keys generate
`, csrfKeyPath, executable)
}
return err
}
return nil
}
@ -238,6 +264,7 @@ func handleViewLanding(app *App, w http.ResponseWriter, r *http.Request) error {
p := struct {
page.StaticPage
*OAuthButtons
Flashes []template.HTML
Banner template.HTML
Content template.HTML
@ -245,6 +272,7 @@ func handleViewLanding(app *App, w http.ResponseWriter, r *http.Request) error {
ForcedLanding bool
}{
StaticPage: pageForReq(app, r),
OAuthButtons: NewOAuthButtons(app.Config()),
ForcedLanding: forceLanding,
}
@ -289,7 +317,7 @@ func handleTemplatedPage(app *App, w http.ResponseWriter, r *http.Request, t *te
}{
StaticPage: pageForReq(app, r),
}
if r.URL.Path == "/about" || r.URL.Path == "/privacy" {
if r.URL.Path == "/about" || r.URL.Path == "/contact" || r.URL.Path == "/privacy" {
var c *instanceContent
var err error
@ -300,6 +328,12 @@ func handleTemplatedPage(app *App, w http.ResponseWriter, r *http.Request, t *te
p.AboutStats = &InstanceStats{}
p.AboutStats.NumPosts, _ = app.db.GetTotalPosts()
p.AboutStats.NumBlogs, _ = app.db.GetTotalCollections()
} else if r.URL.Path == "/contact" {
c, err = getContactPage(app)
if c.Updated.IsZero() {
// Page was never set up, so return 404
return ErrPostNotFound
}
} else {
c, err = getPrivacyPage(app)
}
@ -330,6 +364,11 @@ func pageForReq(app *App, r *http.Request) page.StaticPage {
Version: "v" + softwareVer,
}
// Use custom style, if file exists
if _, err := os.Stat(filepath.Join(app.cfg.Server.StaticParentDir, staticDir, "local", "custom.css")); err == nil {
p.CustomCSS = true
}
// Add user information, if given
var u *User
accessToken := r.FormValue("t")
@ -387,6 +426,19 @@ func Initialize(apper Apper, debug bool) (*App, error) {
return nil, fmt.Errorf("connect to DB: %s", err)
}
initActivityPub(apper.App())
if apper.App().cfg.Email.Domain != "" || apper.App().cfg.Email.MailgunPrivate != "" {
if apper.App().cfg.Email.Domain == "" {
log.Error("[FAILED] Starting publish jobs queue: no [letters]domain config value set.")
} else if apper.App().cfg.Email.MailgunPrivate == "" {
log.Error("[FAILED] Starting publish jobs queue: no [letters]mailgun_private config value set.")
} else {
log.Info("Starting publish jobs queue...")
go startPublishJobsQueue(apper.App())
}
}
// Handle local timeline, if enabled
if apper.App().cfg.App.LocalTimeline {
log.Info("Initializing local timeline...")
@ -413,6 +465,11 @@ func Serve(app *App, r *mux.Router) {
os.Exit(0)
}()
// Start gopher server
if app.cfg.Server.GopherPort > 0 && !app.cfg.App.Private {
go initGopher(app)
}
// Start web application server
var bindAddress = app.cfg.Server.Bind
if bindAddress == "" {
@ -470,9 +527,41 @@ requests. We recommend supplying a valid host name.`)
err = http.ListenAndServeTLS(fmt.Sprintf("%s:443", bindAddress), app.cfg.Server.TLSCertPath, app.cfg.Server.TLSKeyPath, r)
}
} else {
log.Info("Serving on http://%s:%d\n", bindAddress, app.cfg.Server.Port)
network := "tcp"
protocol := "http"
if strings.HasPrefix(bindAddress, "/") {
network = "unix"
protocol = "http+unix"
// old sockets will remain after server closes;
// we need to delete them in order to open new ones
err = os.Remove(bindAddress)
if err != nil && !os.IsNotExist(err) {
log.Error("%s already exists but could not be removed: %v", bindAddress, err)
os.Exit(1)
}
} else {
bindAddress = fmt.Sprintf("%s:%d", bindAddress, app.cfg.Server.Port)
}
log.Info("Serving on %s://%s", protocol, bindAddress)
log.Info("---")
err = http.ListenAndServe(fmt.Sprintf("%s:%d", bindAddress, app.cfg.Server.Port), r)
listener, err := net.Listen(network, bindAddress)
if err != nil {
log.Error("Could not bind to address: %v", err)
os.Exit(1)
}
if network == "unix" {
err = os.Chmod(bindAddress, 0o666)
if err != nil {
log.Error("Could not update socket permissions: %v", err)
os.Exit(1)
}
}
defer listener.Close()
err = http.Serve(listener, r)
}
if err != nil {
log.Error("Unable to start: %v", err)
@ -496,8 +585,8 @@ func (app *App) InitDecoder() {
// tests the connection.
func ConnectToDatabase(app *App) error {
// Check database configuration
if app.cfg.Database.Type == driverMySQL && (app.cfg.Database.User == "" || app.cfg.Database.Password == "") {
return fmt.Errorf("Database user or password not set.")
if app.cfg.Database.Type == driverMySQL && app.cfg.Database.User == "" {
return fmt.Errorf("Database user not set.")
}
if app.cfg.Database.Host == "" {
app.cfg.Database.Host = "localhost"
@ -588,7 +677,7 @@ func DoConfig(app *App, configSections string) {
// Create blog
log.Info("Creating user %s...\n", u.Username)
err = app.db.CreateUser(app.cfg, u, app.cfg.App.SiteName)
err = app.db.CreateUser(app.cfg, u, app.cfg.App.SiteName, "")
if err != nil {
log.Error("Unable to create user: %s", err)
os.Exit(1)
@ -628,6 +717,10 @@ func GenerateKeyFiles(app *App) error {
if err != nil {
keyErrs = err
}
err = generateKey(csrfKeyPath)
if err != nil {
keyErrs = err
}
return keyErrs
}
@ -748,7 +841,7 @@ func connectToDatabase(app *App) {
var db *sql.DB
var err error
if app.cfg.Database.Type == driverMySQL {
db, err = sql.Open(app.cfg.Database.Type, fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=true&loc=%s", app.cfg.Database.User, app.cfg.Database.Password, app.cfg.Database.Host, app.cfg.Database.Port, app.cfg.Database.Database, url.QueryEscape(time.Local.String())))
db, err = sql.Open(app.cfg.Database.Type, fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=true&loc=%s&tls=%t", app.cfg.Database.User, app.cfg.Database.Password, app.cfg.Database.Host, app.cfg.Database.Port, app.cfg.Database.Database, url.QueryEscape(time.Local.String()), app.cfg.Database.TLS))
db.SetMaxOpenConns(50)
} else if app.cfg.Database.Type == driverSQLite {
if !SQLiteEnabled {
@ -760,7 +853,7 @@ func connectToDatabase(app *App) {
os.Exit(1)
}
db, err = sql.Open("sqlite3_with_regex", app.cfg.Database.FileName+"?parseTime=true&cached=shared")
db.SetMaxOpenConns(1)
db.SetMaxOpenConns(2)
} else {
log.Error("Invalid database type '%s'. Only 'mysql' and 'sqlite3' are supported right now.", app.cfg.Database.Type)
os.Exit(1)
@ -775,6 +868,16 @@ func connectToDatabase(app *App) {
func shutdown(app *App) {
log.Info("Closing database connection...")
app.db.Close()
if strings.HasPrefix(app.cfg.Server.Bind, "/") {
// Clean up socket
log.Info("Removing socket file...")
err := os.Remove(app.cfg.Server.Bind)
if err != nil {
log.Error("Unable to remove socket: %s", err)
os.Exit(1)
}
log.Info("Success.")
}
}
// CreateUser creates a new admin or normal user from the given credentials.
@ -829,7 +932,7 @@ func CreateUser(apper Apper, username, password string, isAdmin bool) error {
userType = "admin"
}
log.Info("Creating %s %s...", userType, usernameDesc)
err = apper.App().db.CreateUser(apper.App().Config(), u, desiredUsername)
err = apper.App().db.CreateUser(apper.App().Config(), u, desiredUsername, "")
if err != nil {
return fmt.Errorf("Unable to create user: %s", err)
}
@ -837,15 +940,18 @@ func CreateUser(apper Apper, username, password string, isAdmin bool) error {
return nil
}
func adminInitDatabase(app *App) error {
schemaFileName := "schema.sql"
if app.cfg.Database.Type == driverSQLite {
schemaFileName = "sqlite.sql"
}
//go:embed schema.sql
var schemaSql string
schema, err := Asset(schemaFileName)
if err != nil {
return fmt.Errorf("Unable to load schema file: %v", err)
//go:embed sqlite.sql
var sqliteSql string
func adminInitDatabase(app *App) error {
var schema string
if app.cfg.Database.Type == driverSQLite {
schema = sqliteSql
} else {
schema = schemaSql
}
tblReg := regexp.MustCompile("CREATE TABLE (IF NOT EXISTS )?`([a-z_]+)`")
@ -861,7 +967,7 @@ func adminInitDatabase(app *App) error {
} else {
log.Info("Creating table ??? (Weird query) No match in: %v", parts)
}
_, err = app.db.Exec(q)
_, err := app.db.Exec(q)
if err != nil {
log.Error("%s", err)
} else {
@ -871,7 +977,7 @@ func adminInitDatabase(app *App) error {
// Set up migrations table
log.Info("Initializing appmigrations table...")
err = migrations.SetInitialMigrations(migrations.NewDatastore(app.db.DB, app.db.driverName))
err := migrations.SetInitialMigrations(migrations.NewDatastore(app.db.DB, app.db.driverName))
if err != nil {
return fmt.Errorf("Unable to set initial migrations: %v", err)
}
@ -885,3 +991,13 @@ func adminInitDatabase(app *App) error {
log.Info("Done.")
return nil
}
// ServerUserAgent returns a User-Agent string to use in external requests. The
// hostName parameter may be left empty.
func ServerUserAgent(hostName string) string {
hostUAStr := ""
if hostName != "" {
hostUAStr = "; +" + hostName
}
return "Go (" + serverSoftware + "/" + softwareVer + hostUAStr + ")"
}

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018 A Bunch Tell LLC.
* Copyright © 2018 Musing Studio LLC.
*
* This file is part of WriteFreely.
*

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018-2020 A Bunch Tell LLC.
* Copyright © 2018-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -11,7 +11,8 @@
package author
import (
"github.com/writeas/writefreely/config"
"github.com/writeas/web-core/log"
"github.com/writefreely/writefreely/config"
"os"
"path/filepath"
"regexp"
@ -113,10 +114,17 @@ func IsValidUsername(cfg *config.Config, username string) bool {
// Username is invalid if page with the same name exists. So traverse
// available pages, adding them to reservedUsernames map that'll be checked
// later.
filepath.Walk(filepath.Join(cfg.Server.PagesParentDir, "pages"), func(path string, i os.FileInfo, err error) error {
err := filepath.Walk(filepath.Join(cfg.Server.PagesParentDir, "pages"), func(path string, i os.FileInfo, err error) error {
if err != nil {
return err
}
reservedUsernames[i.Name()] = true
return nil
})
if err != nil {
log.Error("[IMPORTANT WARNING]: Could not determine IsValidUsername! %s", err)
return false
}
// Username is invalid if it is reserved!
if _, reserved := reservedUsernames[username]; reserved {

View File

@ -1,105 +0,0 @@
// +build wflib
package writefreely
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"strings"
)
func bindata_read(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
var buf bytes.Buffer
_, err = io.Copy(&buf, gz)
gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
return buf.Bytes(), nil
}
var _schema_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x59\x5f\x6f\xa3\x38\x10\x7f\xef\xa7\xf0\xdb\xa6\x52\x23\x6d\x7a\xdd\xaa\xba\xd3\x3e\x64\x53\x76\x2f\xba\x94\xee\x25\x44\xba\x7d\x02\x03\x93\xd4\xaa\xb1\x91\x6d\x92\xe6\xdb\x9f\x8c\x49\x08\x86\x24\xd0\xdb\x3b\x71\x7d\x2a\xcc\x6f\x8c\xfd\x9b\x3f\x9e\x99\x0c\x87\x57\xc3\x21\x7a\xc4\x0a\x87\x58\xc2\xaf\x28\xd8\x0a\xa2\x60\x25\x00\xe8\x2e\xb8\x1a\x0e\xaf\xb4\x78\xf8\xce\x3f\xad\xac\xf5\x3d\x1c\x52\x40\x52\x89\x2c\x52\x99\x00\xb4\xe2\x02\xa9\xfc\x5d\x80\xa3\x08\xa4\x54\xfc\x15\x98\x34\xdf\x9b\xcc\x9d\xb1\xe7\x20\x6f\xfc\x65\xe6\xa0\xe9\x57\xe4\x3e\x7b\xc8\xf9\x6b\xba\xf0\x16\x16\x1a\x0d\xae\x10\x0a\xf2\x87\x00\x85\x84\x61\xb1\x1b\x8c\xee\xaf\x73\x05\x77\x39\x9b\xdd\x68\x71\x26\x41\xf8\x24\x0e\x10\x61\x6a\x60\x0b\x65\x16\xf3\x00\x29\xc2\x76\x5a\x3a\x2a\xa5\xe8\xd1\xf9\x3a\x5e\xce\x3c\xf4\xe1\xe3\x87\x1c\xc9\x19\xf8\x8a\x24\xd0\x0e\x1d\x09\xc0\x0a\xe2\x00\xc5\x58\x81\x56\xab\x43\x27\xcb\xf9\xdc\x71\x3d\xdf\x9b\x3e\x39\x0b\x6f\xfc\xf4\x3d\x57\x84\xb7\x94\x08\x90\x47\x8a\x7b\x7c\xf5\x40\x78\x0d\x4c\x05\x68\x83\x45\xf4\x82\xc5\xe0\xf6\xd3\xa7\xeb\x1a\xf2\xfb\x7c\xfa\x34\x9e\xff\x40\x7f\x38\x3f\xd0\xa0\xa0\xe9\xfa\xea\x1a\x39\xee\xb7\xa9\xeb\x7c\x9e\x32\xc6\x1f\xbf\x94\xfb\xf9\x7d\x3c\x5f\x38\xde\x67\x8a\x15\x61\xa3\xdf\xfe\x75\xb3\xa7\x69\xc4\x99\xd2\xa7\xb8\x6c\xf4\x12\x6b\x4c\xae\xcd\xb9\x3f\xfa\x2f\xb6\x4d\x0f\xd0\x04\x62\x92\x25\x0a\xde\x54\x7e\xb8\xf1\xc4\x73\xe6\x68\xe1\x78\x28\x53\xab\x07\x34\x79\x9e\xcd\xf4\x17\xf5\x83\x1f\x12\x66\x79\x4d\x1a\xbf\xcb\x80\x55\xce\x49\xdc\x2b\xc2\x13\xb2\x16\x58\x11\xde\x18\x68\x16\xc0\x10\xbd\x01\x21\x09\x67\x26\x78\x46\x23\x8b\x69\x03\x6f\x64\x29\x97\x0b\x90\x19\x55\x01\xca\x4d\xb0\x97\xf4\x85\x8f\x88\x53\x0a\x91\x3e\x2c\x56\x4a\x90\x30\x53\xd0\x22\xff\x34\x6a\x19\xae\x4a\xd1\xc9\x74\x73\xd0\x29\xdd\x77\x74\xfb\x60\x81\x36\x98\x66\x60\x85\x76\xdd\x7f\x93\xf0\xae\xe2\xc2\x49\x78\x57\xf3\xe2\xaa\x33\x56\xf7\x77\x73\xb4\x99\xde\xf8\x68\xb9\xc5\x57\xd8\x75\xb2\x46\x8e\x6f\x6d\x87\x34\x0b\x29\x89\xfc\x57\xd8\x05\x28\xa4\x3c\xb4\xa4\x82\x6c\xb0\x82\x13\xe2\x73\xa4\xf6\x90\xc8\x14\x4b\xb9\xe5\x22\xee\xc4\x66\xa9\xd4\x9e\xd2\x42\x25\x40\xb9\xd7\xde\x7f\xbc\xfe\x3f\xb3\x26\x20\x26\x02\x22\xd5\x89\xb5\x52\xc9\xb0\x96\x0a\xd8\xf8\x98\x12\x2c\x8f\xc2\xfd\xa3\x45\x4c\xc0\x60\x7b\x11\x54\x65\xef\x68\xdd\x1e\x52\xd7\x89\x32\x79\x74\xa1\x5b\x5e\x85\xc6\x4b\xef\xd9\x9f\xba\x93\xb9\xf3\xe4\xb8\x9e\xc9\x9f\x0d\x3c\xb5\x4f\x8d\xb5\x4a\x4a\x11\x45\x7f\x4e\xa6\x0d\x62\x90\x91\x20\xa9\xca\x2f\xcb\xc3\xfe\xee\x3b\xed\xaf\x5a\x99\xaa\x1d\x05\x5f\xbe\x00\x14\x17\xa8\x79\x9b\x7f\xa4\xb8\x51\x5b\xaf\x9c\xab\xae\xb8\x48\xf0\x51\xc9\xf8\x50\x2f\x18\x4d\xe6\x8b\x76\x8d\x35\xae\xa9\x82\xb7\xec\x4c\x35\xbd\x21\xb0\xf5\x23\x9e\xe9\xe2\xab\x41\x5e\xaf\x8d\xf4\xdb\xa5\x3b\xfd\x73\xe9\xe4\x2f\xf7\xf6\x1d\x04\x3d\xf3\xee\x94\xcb\x36\xa9\xc0\xc0\x4a\x8f\x2e\x9c\xc0\xee\x39\x68\xb6\xb6\x7c\xb8\x66\x88\x84\xc7\x64\xb5\xf3\x8b\xd6\xc6\xd4\xb9\xb7\x0d\x38\xed\x07\x3e\x4e\x53\xc0\x02\xb3\x08\x0a\xe8\x5d\x53\x67\xc2\xb8\x48\x4c\x73\x42\x31\x5b\x67\x78\xbd\x47\x37\xad\x2b\x14\xad\x38\xc1\x4f\xf0\x94\xda\x12\xcd\x97\x4a\xfd\x4b\x84\x31\x88\xfd\x94\x4b\x62\xa2\xeb\xe8\x8b\x4b\x77\x31\xfd\xe6\x3a\x8f\x0d\x8b\xef\x1b\x30\x5d\x95\x4a\x85\x93\xb4\x6d\x07\x76\xa8\xfc\x3b\x6b\x5e\x70\x7f\x3b\xdd\xfc\x93\xec\x70\xe8\x71\xba\x25\x82\x8e\xe1\x48\x62\xdf\x38\x6b\xbd\x78\xcc\xdf\xd7\x14\x4a\xa3\x0f\xca\xff\x6f\x0e\x6b\xe7\x98\xc2\x73\x0a\xd4\xde\x8f\x6e\x7a\xd5\x2b\x09\x48\xb8\x82\x15\xa7\x94\x6f\x5b\xc4\x7d\x15\x7e\xb2\x64\xaa\xf5\x4f\x46\xcf\xaf\x4c\x28\x6a\xa0\xd3\xa3\x84\xcb\x25\xbe\xf5\x81\x9e\xf1\xab\xb7\xd5\xae\xce\xb7\xf0\xf5\x21\x40\x7e\x75\x77\xe7\xf6\x6c\x1f\x70\x39\x3e\x8c\xc5\x0f\x1e\xdf\x7f\xb6\x3b\x51\x6d\xd7\x66\xc7\xec\x35\x16\x67\x91\xe2\x86\x8a\xd3\x56\x21\x2c\xe4\x6f\xe7\x00\xf2\x05\x0b\x88\xfd\x4b\xb8\xcb\xb6\xb1\xe2\x6f\x50\x6e\xaf\x37\x76\xd1\x24\x77\x99\x3d\x58\x78\x63\x9d\xb3\xe3\xcd\x86\x79\xc3\xfd\xdd\x7f\x34\x6e\xd8\x6f\xac\x97\x83\x06\xbd\x39\xc2\x36\xa4\x99\xf7\x8a\xd8\x2a\xe7\x6c\x8a\xab\x75\x4e\x7d\x44\x86\xdf\x74\x42\x90\x01\x92\x09\xa6\xf4\x64\x2d\x74\x36\xc9\xb7\x99\x0a\x13\x86\x23\x45\x36\xcd\xf3\xe9\x3e\xd1\xde\xd2\xd1\x3b\x76\x86\x5a\x85\xe1\x04\xde\xdd\x1c\x5e\x1a\x66\x54\x57\x32\x7c\x1d\x16\x32\x8f\xf5\x75\x20\xc1\x84\xe6\x5b\x2a\x7e\x9d\x68\x9c\xd3\xbf\xfb\xd7\x82\xcb\x59\xb0\xa4\x65\x50\xfe\xdf\xab\x28\x94\x26\xce\xe2\x53\x61\x78\x90\x17\xee\x90\x3f\xf9\x27\xc3\xf1\xe4\x7d\xdf\xfa\xcc\x7f\x07\x00\x00\xff\xff\xbe\x79\x68\xa8\x10\x1b\x00\x00")
func schema_sql() ([]byte, error) {
return bindata_read(
_schema_sql,
"schema.sql",
)
}
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
return f()
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
for name := range _bindata {
names = append(names, name)
}
return names
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() ([]byte, error){
"schema.sql": schema_sql,
}
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
// data/
// foo.txt
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
}
}
if node.Func != nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for name := range node.Children {
rv = append(rv, name)
}
return rv, nil
}
type _bintree_t struct {
Func func() ([]byte, error)
Children map[string]*_bintree_t
}
var _bintree = &_bintree_t{nil, map[string]*_bintree_t{
"schema.sql": &_bintree_t{schema_sql, map[string]*_bintree_t{
}},
}}

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018 A Bunch Tell LLC.
* Copyright © 2018 Musing Studio LLC.
*
* This file is part of WriteFreely.
*

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2020 A Bunch Tell LLC.
* Copyright © 2020-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -11,9 +11,8 @@
package main
import (
"github.com/writeas/writefreely"
"github.com/urfave/cli/v2"
"github.com/writefreely/writefreely"
)
var (

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2020 A Bunch Tell LLC.
* Copyright © 2020-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -11,9 +11,8 @@
package main
import (
"github.com/writeas/writefreely"
"github.com/urfave/cli/v2"
"github.com/writefreely/writefreely"
)
var (

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2020 A Bunch Tell LLC.
* Copyright © 2020-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -11,9 +11,8 @@
package main
import (
"github.com/writeas/writefreely"
"github.com/urfave/cli/v2"
"github.com/writefreely/writefreely"
)
var (

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018-2020 A Bunch Tell LLC.
* Copyright © 2018-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -15,11 +15,10 @@ import (
"os"
"strings"
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely"
"github.com/gorilla/mux"
"github.com/urfave/cli/v2"
"github.com/writeas/web-core/log"
"github.com/writefreely/writefreely"
)
func main() {

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2020 A Bunch Tell LLC.
* Copyright © 2020-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -13,9 +13,8 @@ package main
import (
"fmt"
"github.com/writeas/writefreely"
"github.com/urfave/cli/v2"
"github.com/writefreely/writefreely"
)
var (

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2020 A Bunch Tell LLC.
* Copyright © 2020-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -11,11 +11,10 @@
package main
import (
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely"
"github.com/gorilla/mux"
"github.com/urfave/cli/v2"
"github.com/writeas/web-core/log"
"github.com/writefreely/writefreely"
)
var (

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018-2020 A Bunch Tell LLC.
* Copyright © 2018-2022 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -24,15 +24,26 @@ import (
"unicode"
"github.com/gorilla/mux"
stripmd "github.com/writeas/go-strip-markdown/v2"
"github.com/writeas/impart"
"github.com/writeas/web-core/activitystreams"
"github.com/writeas/web-core/auth"
"github.com/writeas/web-core/bots"
"github.com/writeas/web-core/i18n"
"github.com/writeas/web-core/log"
waposts "github.com/writeas/web-core/posts"
"github.com/writeas/writefreely/author"
"github.com/writeas/writefreely/config"
"github.com/writeas/writefreely/page"
"github.com/writeas/web-core/posts"
"github.com/writefreely/writefreely/author"
"github.com/writefreely/writefreely/config"
"github.com/writefreely/writefreely/page"
"github.com/writefreely/writefreely/spam"
"golang.org/x/net/idna"
)
const (
collAttrLetterReplyTo = "letter_reply_to"
collMaxLengthTitle = 255
collMaxLengthDescription = 160
)
type (
@ -47,6 +58,7 @@ type (
Language string `schema:"lang" json:"lang,omitempty"`
StyleSheet string `datastore:"style_sheet" schema:"style_sheet" json:"style_sheet"`
Script string `datastore:"script" schema:"script" json:"script,omitempty"`
Signature string `datastore:"post_signature" schema:"signature" json:"-"`
Public bool `datastore:"public" json:"public"`
Visibility collVisibility `datastore:"private" json:"-"`
Format string `datastore:"format" json:"format,omitempty"`
@ -55,6 +67,9 @@ type (
PublicOwner bool `datastore:"public_owner" json:"-"`
URL string `json:"url,omitempty"`
Monetization string `json:"monetization_pointer,omitempty"`
Verification string `json:"verification_link"`
db *datastore
hostName string
}
@ -68,11 +83,20 @@ type (
DisplayCollection struct {
*CollectionObj
Prefix string
NavSuffix string
IsTopLevel bool
CurrentPage int
TotalPages int
Silenced bool
}
CollectionNav struct {
*Collection
Path string
SingleUser bool
CanPost bool
}
SubmittedCollection struct {
// Data used for updating a given collection
ID int64
@ -83,16 +107,21 @@ type (
Privacy int `schema:"privacy" json:"privacy"`
Pass string `schema:"password" json:"password"`
MathJax bool `schema:"mathjax" json:"mathjax"`
EmailSubs bool `schema:"email_subs" json:"email_subs"`
Handle string `schema:"handle" json:"handle"`
// Actual collection values updated in the DB
Alias *string `schema:"alias" json:"alias"`
Title *string `schema:"title" json:"title"`
Description *string `schema:"description" json:"description"`
StyleSheet *sql.NullString `schema:"style_sheet" json:"style_sheet"`
Script *sql.NullString `schema:"script" json:"script"`
Visibility *int `schema:"visibility" json:"public"`
Format *sql.NullString `schema:"format" json:"format"`
Alias *string `schema:"alias" json:"alias"`
Title *string `schema:"title" json:"title"`
Description *string `schema:"description" json:"description"`
StyleSheet *string `schema:"style_sheet" json:"style_sheet"`
Script *string `schema:"script" json:"script"`
Signature *string `schema:"signature" json:"signature"`
Monetization *string `schema:"monetization_pointer" json:"monetization_pointer"`
Verification *string `schema:"verification_link" json:"verification_link"`
LetterReply *string `schema:"letter_reply" json:"letter_reply"`
Visibility *int `schema:"visibility" json:"public"`
Format *sql.NullString `schema:"format" json:"format"`
}
CollectionFormat struct {
Format string
@ -105,6 +134,8 @@ type (
// User-related fields
isCollOwner bool
isAuthorized bool
}
)
@ -175,6 +206,11 @@ func (c *Collection) NewFormat() *CollectionFormat {
return cf
}
func (c *Collection) IsInstanceColl() bool {
ur, _ := url.Parse(c.hostName)
return c.Alias == ur.Host
}
func (c *Collection) IsUnlisted() bool {
return c.Visibility == 0
}
@ -224,13 +260,17 @@ func (c *Collection) DisplayCanonicalURL() string {
if p == "/" {
p = ""
}
return u.Hostname() + p
d := u.Hostname()
d, _ = idna.ToUnicode(d)
return d + p
}
// RedirectingCanonicalURL returns the fully-qualified canonical URL for the Collection, with a trailing slash. The
// hostName field needs to be populated for this to work correctly.
func (c *Collection) RedirectingCanonicalURL(isRedir bool) string {
if c.hostName == "" {
// If this is true, the human programmers screwed up. So ask for a bug report and fail, fail, fail
log.Error("[PROGRAMMER ERROR] WARNING: Collection.hostName is empty! Federation and many other things will fail! If you're seeing this in the wild, please report this bug and let us know what you were doing just before this: https://github.com/writeas/writefreely/issues/new?template=bug_report.md")
log.Error("[PROGRAMMER ERROR] WARNING: Collection.hostName is empty! Federation and many other things will fail! If you're seeing this in the wild, please report this bug and let us know what you were doing just before this: https://github.com/writefreely/writefreely/issues/new?template=bug_report.md")
}
if isSingleUser {
return c.hostName + "/"
@ -241,16 +281,16 @@ func (c *Collection) RedirectingCanonicalURL(isRedir bool) string {
// PrevPageURL provides a full URL for the previous page of collection posts,
// returning a /page/N result for pages >1
func (c *Collection) PrevPageURL(prefix string, n int, tl bool) string {
func (c *Collection) PrevPageURL(prefix, navSuffix string, n int, tl bool) string {
u := ""
if n == 2 {
// Previous page is 1; no need for /page/ prefix
if prefix == "" {
u = "/"
u = navSuffix + "/"
}
// Else leave off trailing slash
} else {
u = fmt.Sprintf("/page/%d", n-1)
u = fmt.Sprintf("%s/page/%d", navSuffix, n-1)
}
if tl {
@ -260,11 +300,12 @@ func (c *Collection) PrevPageURL(prefix string, n int, tl bool) string {
}
// NextPageURL provides a full URL for the next page of collection posts
func (c *Collection) NextPageURL(prefix string, n int, tl bool) string {
func (c *Collection) NextPageURL(prefix, navSuffix string, n int, tl bool) string {
if tl {
return fmt.Sprintf("/page/%d", n+1)
return fmt.Sprintf("%s/page/%d", navSuffix, n+1)
}
return fmt.Sprintf("/%s%s/page/%d", prefix, c.Alias, n+1)
return fmt.Sprintf("/%s%s%s/page/%d", prefix, c.Alias, navSuffix, n+1)
}
func (c *Collection) DisplayTitle() string {
@ -338,6 +379,51 @@ func (c *Collection) RenderMathJax() bool {
return c.db.CollectionHasAttribute(c.ID, "render_mathjax")
}
func (c *Collection) EmailSubsEnabled() bool {
return c.db.CollectionHasAttribute(c.ID, "email_subs")
}
func (c *Collection) MonetizationURL() string {
if c.Monetization == "" {
return ""
}
return strings.Replace(c.Monetization, "$", "https://", 1)
}
// DisplayDescription returns the description with rendered Markdown and HTML.
func (c *Collection) DisplayDescription() *template.HTML {
if c.Description == "" {
s := template.HTML("")
return &s
}
t := template.HTML(posts.ApplyBasicAccessibleMarkdown([]byte(c.Description)))
return &t
}
// PlainDescription returns the description with all Markdown and HTML removed.
func (c *Collection) PlainDescription() string {
if c.Description == "" {
return ""
}
desc := stripHTMLWithoutEscaping(c.Description)
desc = stripmd.Strip(desc)
return desc
}
func (c CollectionPage) DisplayMonetization() string {
return displayMonetization(c.Monetization, c.Alias)
}
func (c *DisplayCollection) Direction() string {
if c.Language == "" {
return "auto"
}
if i18n.LangIsRTL(c.Language) {
return "rtl"
}
return "ltr"
}
func newCollection(app *App, w http.ResponseWriter, r *http.Request) error {
reqJSON := IsJSON(r)
alias := r.FormValue("alias")
@ -447,8 +533,7 @@ func apiCheckCollectionPermissions(app *App, r *http.Request, c *Collection) (in
// fetchCollection handles the API endpoint for retrieving collection data.
func fetchCollection(app *App, w http.ResponseWriter, r *http.Request) error {
accept := r.Header.Get("Accept")
if strings.Contains(accept, "application/activity+json") {
if IsActivityPubRequest(r) {
return handleFetchCollectionActivities(app, w, r)
}
@ -523,11 +608,11 @@ func fetchCollectionPosts(app *App, w http.ResponseWriter, r *http.Request) erro
}
}
posts, err := app.db.GetPosts(app.cfg, c, page, isCollOwner, false, false)
ps, err := app.db.GetPosts(app.cfg, c, page, isCollOwner, false, false)
if err != nil {
return err
}
coll := &CollectionObj{Collection: *c, Posts: posts}
coll := &CollectionObj{Collection: *c, Posts: ps}
app.db.GetPostsCount(coll, isCollOwner)
// Strip non-public information
coll.Collection.ForPublic()
@ -535,7 +620,7 @@ func fetchCollectionPosts(app *App, w http.ResponseWriter, r *http.Request) erro
// Transform post bodies if needed
if r.FormValue("body") == "html" {
for _, p := range *coll.Posts {
p.Content = waposts.ApplyMarkdown([]byte(p.Content))
p.Content = posts.ApplyMarkdown([]byte(p.Content))
}
}
@ -548,12 +633,45 @@ type CollectionPage struct {
IsCustomDomain bool
IsWelcome bool
IsOwner bool
IsCollLoggedIn bool
Honeypot string
IsSubscriber bool
CanPin bool
Username string
Monetization string
Flash template.HTML
Collections *[]Collection
PinnedPosts *[]PublicPost
IsAdmin bool
CanInvite bool
IsAdmin bool
CanInvite bool
// Helper field for Chorus mode
CollAlias string
}
type TagCollectionPage struct {
CollectionPage
Tag string
}
func (tcp TagCollectionPage) PrevPageURL(prefix string, n int, tl bool) string {
u := fmt.Sprintf("/tag:%s", tcp.Tag)
if n > 2 {
u += fmt.Sprintf("/page/%d", n-1)
}
if tl {
return u
}
return "/" + prefix + tcp.Alias + u
}
func (tcp TagCollectionPage) NextPageURL(prefix string, n int, tl bool) string {
if tl {
return fmt.Sprintf("/tag:%s/page/%d", tcp.Tag, n+1)
}
return fmt.Sprintf("/%s%s/tag:%s/page/%d", prefix, tcp.Alias, tcp.Tag, n+1)
}
func NewCollectionObj(c *Collection) *CollectionObj {
@ -666,9 +784,9 @@ func processCollectionPermissions(app *App, cr *collectionReq, u *User, w http.R
}
// See if we've authorized this collection
authd := isAuthorizedForCollection(app, c.Alias, r)
cr.isAuthorized = isAuthorizedForCollection(app, c.Alias, r)
if !authd {
if !cr.isAuthorized {
p := struct {
page.StaticPage
*CollectionObj
@ -721,14 +839,14 @@ func newDisplayCollection(c *Collection, cr *collectionReq, page int) *DisplayCo
return coll
}
// getCollectionPage returns the collection page as an int. If the parsed page value is not
// greater than 0 then the default value of 1 is returned.
func getCollectionPage(vars map[string]string) int {
page := 1
var p int
p, _ = strconv.Atoi(vars["page"])
if p > 0 {
page = p
if p, _ := strconv.Atoi(vars["page"]); p > 0 {
return p
}
return page
return 1
}
// handleViewCollection displays the requested Collection
@ -761,7 +879,7 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
}
// Serve ActivityStreams data now, if requested
if strings.Contains(r.Header.Get("Accept"), "application/activity+json") {
if IsActivityPubRequest(r) {
ac := c.PersonObject()
ac.Context = []interface{}{activitystreams.Namespace}
setCacheControl(w, apCacheTime)
@ -786,9 +904,16 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
// Serve collection
displayPage := CollectionPage{
DisplayCollection: coll,
IsCollLoggedIn: cr.isAuthorized,
StaticPage: pageForReq(app, r),
IsCustomDomain: cr.isCustomDomain,
IsWelcome: r.FormValue("greeting") != "",
Honeypot: spam.HoneypotFieldName(),
CollAlias: c.Alias,
}
flashes, _ := getSessionFlashes(app, w, r, nil)
for _, f := range flashes {
displayPage.Flash = template.HTML(f)
}
displayPage.IsAdmin = u != nil && u.IsAdmin()
displayPage.CanInvite = canUserInvite(app.cfg, displayPage.IsAdmin)
@ -796,6 +921,7 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
if u != nil {
displayPage.Username = u.Username
displayPage.IsOwner = u.ID == coll.OwnerID
displayPage.IsSubscriber = u.IsEmailSubscriber(app, coll.ID)
if displayPage.IsOwner {
// Add in needed information for users viewing their own collection
owner = u
@ -827,6 +953,7 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
// Add more data
// TODO: fix this mess of collections inside collections
displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, isOwner)
displayPage.Monetization = app.db.GetCollectionAttribute(coll.ID, "monetization_pointer")
collTmpl := "collection"
if app.cfg.App.Chorus {
@ -894,16 +1021,29 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e
coll := newDisplayCollection(c, cr, page)
taggedPostIDs, err := app.db.GetAllPostsTaggedIDs(c, tag, cr.isCollOwner)
if err != nil {
return err
}
ttlPosts := len(taggedPostIDs)
pagePosts := coll.Format.PostsPerPage()
coll.TotalPages = int(math.Ceil(float64(ttlPosts) / float64(pagePosts)))
if coll.TotalPages > 0 && page > coll.TotalPages {
redirURL := fmt.Sprintf("/page/%d", coll.TotalPages)
if !app.cfg.App.SingleUser {
redirURL = fmt.Sprintf("/%s%s%s", cr.prefix, coll.Alias, redirURL)
}
return impart.HTTPError{http.StatusFound, redirURL}
}
coll.Posts, _ = app.db.GetPostsTagged(app.cfg, c, tag, page, cr.isCollOwner)
if coll.Posts != nil && len(*coll.Posts) == 0 {
return ErrCollectionPageNotFound
}
// Serve collection
displayPage := struct {
CollectionPage
Tag string
}{
displayPage := TagCollectionPage{
CollectionPage: CollectionPage{
DisplayCollection: coll,
StaticPage: pageForReq(app, r),
@ -945,6 +1085,7 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e
// Add more data
// TODO: fix this mess of collections inside collections
displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, isOwner)
displayPage.Monetization = app.db.GetCollectionAttribute(coll.ID, "monetization_pointer")
err = templates["collection-tags"].ExecuteTemplate(w, "collection-tags", displayPage)
if err != nil {
@ -954,6 +1095,111 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e
return nil
}
func handleViewCollectionLang(app *App, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
lang := vars["lang"]
cr := &collectionReq{}
err := processCollectionRequest(cr, vars, w, r)
if err != nil {
return err
}
u, err := checkUserForCollection(app, cr, r, false)
if err != nil {
return err
}
page := getCollectionPage(vars)
c, err := processCollectionPermissions(app, cr, u, w, r)
if c == nil || err != nil {
return err
}
coll := newDisplayCollection(c, cr, page)
coll.Language = lang
coll.NavSuffix = fmt.Sprintf("/lang:%s", lang)
ttlPosts, err := app.db.GetCollLangTotalPosts(coll.ID, lang)
if err != nil {
log.Error("Unable to getCollLangTotalPosts: %s", err)
}
pagePosts := coll.Format.PostsPerPage()
coll.TotalPages = int(math.Ceil(float64(ttlPosts) / float64(pagePosts)))
if coll.TotalPages > 0 && page > coll.TotalPages {
redirURL := fmt.Sprintf("/lang:%s/page/%d", lang, coll.TotalPages)
if !app.cfg.App.SingleUser {
redirURL = fmt.Sprintf("/%s%s%s", cr.prefix, coll.Alias, redirURL)
}
return impart.HTTPError{http.StatusFound, redirURL}
}
coll.Posts, _ = app.db.GetLangPosts(app.cfg, c, lang, page, cr.isCollOwner)
if err != nil {
return ErrCollectionPageNotFound
}
// Serve collection
displayPage := struct {
CollectionPage
Tag string
}{
CollectionPage: CollectionPage{
DisplayCollection: coll,
StaticPage: pageForReq(app, r),
IsCustomDomain: cr.isCustomDomain,
},
Tag: lang,
}
var owner *User
if u != nil {
displayPage.Username = u.Username
displayPage.IsOwner = u.ID == coll.OwnerID
if displayPage.IsOwner {
// Add in needed information for users viewing their own collection
owner = u
displayPage.CanPin = true
pubColls, err := app.db.GetPublishableCollections(owner, app.cfg.App.Host)
if err != nil {
log.Error("unable to fetch collections: %v", err)
}
displayPage.Collections = pubColls
}
}
isOwner := owner != nil
if !isOwner {
// Current user doesn't own collection; retrieve owner information
owner, err = app.db.GetUserByID(coll.OwnerID)
if err != nil {
// Log the error and just continue
log.Error("Error getting user for collection: %v", err)
}
if owner.IsSilenced() {
return ErrCollectionNotFound
}
}
displayPage.Silenced = owner != nil && owner.IsSilenced()
displayPage.Owner = owner
coll.Owner = displayPage.Owner
// Add more data
// TODO: fix this mess of collections inside collections
displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, isOwner)
displayPage.Monetization = app.db.GetCollectionAttribute(coll.ID, "monetization_pointer")
collTmpl := "collection"
if app.cfg.App.Chorus {
collTmpl = "chorus-collection"
}
err = templates[collTmpl].ExecuteTemplate(w, "collection", displayPage)
if err != nil {
log.Error("Unable to render collection lang page: %v", err)
}
return nil
}
func handleCollectionPostRedirect(app *App, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
slug := vars["slug"]
@ -1038,7 +1284,7 @@ func existingCollection(app *App, w http.ResponseWriter, r *http.Request) error
}
}
err = app.db.UpdateCollection(&c, collAlias)
err = app.db.UpdateCollection(app, &c, collAlias)
if err != nil {
if err, ok := err.(impart.HTTPError); ok {
if reqJSON {
@ -1150,3 +1396,43 @@ func isAuthorizedForCollection(app *App, alias string, r *http.Request) bool {
}
return authd
}
func logOutCollection(app *App, alias string, w http.ResponseWriter, r *http.Request) error {
session, err := app.sessionStore.Get(r, blogPassCookieName)
if err != nil {
return err
}
// Remove this from map of blogs logged into
delete(session.Values, alias)
// If not auth'd with any blog, delete entire cookie
if len(session.Values) == 0 {
session.Options.MaxAge = -1
}
return session.Save(r, w)
}
func handleLogOutCollection(app *App, w http.ResponseWriter, r *http.Request) error {
alias := collectionAliasFromReq(r)
var c *Collection
var err error
if app.cfg.App.SingleUser {
c, err = app.db.GetCollectionByID(1)
} else {
c, err = app.db.GetCollection(alias)
}
if err != nil {
return err
}
if !c.IsProtected() {
// Invalid to log out of this collection
return ErrCollectionPageNotFound
}
err = logOutCollection(app, c.Alias, w, r)
if err != nil {
addSessionFlash(app, w, r, "Logging out failed. Try clearing cookies for this site, instead.", nil)
}
return impart.HTTPError{http.StatusFound, c.CanonicalURL()}
}

View File

@ -1,27 +0,0 @@
[server]
hidden_host =
port = 8080
[database]
type = mysql
username = root
password = changeme
database = writefreely
host = db
port = 3306
[app]
site_name = WriteFreely Example Blog!
host = http://localhost:8080
theme = write
disable_js = false
webfonts = true
single_user = true
open_registration = false
min_username_len = 3
max_blogs = 1
federation = true
public_stats = true
private = false
update_checks = true

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018-2019 A Bunch Tell LLC.
* Copyright © 2018-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -12,9 +12,12 @@
package config
import (
"net/url"
"strings"
"gopkg.in/ini.v1"
"github.com/go-ini/ini"
"github.com/writeas/web-core/log"
"golang.org/x/net/idna"
)
const (
@ -45,6 +48,8 @@ type (
HashSeed string `ini:"hash_seed"`
GopherPort int `ini:"gopher_port"`
Dev bool `ini:"-"`
}
@ -57,6 +62,7 @@ type (
Database string `ini:"database"`
Host string `ini:"host"`
Port int `ini:"port"`
TLS bool `ini:"tls"`
}
WriteAsOauthCfg struct {
@ -78,6 +84,15 @@ type (
CallbackProxyAPI string `ini:"callback_proxy_api"`
}
GiteaOauthCfg struct {
ClientID string `ini:"client_id"`
ClientSecret string `ini:"client_secret"`
Host string `ini:"host"`
DisplayName string `ini:"display_name"`
CallbackProxy string `ini:"callback_proxy"`
CallbackProxyAPI string `ini:"callback_proxy_api"`
}
SlackOauthCfg struct {
ClientID string `ini:"client_id"`
ClientSecret string `ini:"client_secret"`
@ -86,6 +101,24 @@ type (
CallbackProxyAPI string `ini:"callback_proxy_api"`
}
GenericOauthCfg struct {
ClientID string `ini:"client_id"`
ClientSecret string `ini:"client_secret"`
Host string `ini:"host"`
DisplayName string `ini:"display_name"`
CallbackProxy string `ini:"callback_proxy"`
CallbackProxyAPI string `ini:"callback_proxy_api"`
TokenEndpoint string `ini:"token_endpoint"`
InspectEndpoint string `ini:"inspect_endpoint"`
AuthEndpoint string `ini:"auth_endpoint"`
Scope string `ini:"scope"`
AllowDisconnect bool `ini:"allow_disconnect"`
MapUserID string `ini:"map_user_id"`
MapUsername string `ini:"map_username"`
MapDisplayName string `ini:"map_display_name"`
MapEmail string `ini:"map_email"`
}
// AppCfg holds values that affect how the application functions
AppCfg struct {
SiteName string `ini:"site_name"`
@ -109,12 +142,16 @@ type (
// Users
SingleUser bool `ini:"single_user"`
OpenRegistration bool `ini:"open_registration"`
OpenDeletion bool `ini:"open_deletion"`
MinUsernameLen int `ini:"min_username_len"`
MaxBlogs int `ini:"max_blogs"`
// Options for public instances
// Federation
Federation bool `ini:"federation"`
PublicStats bool `ini:"public_stats"`
Federation bool `ini:"federation"`
PublicStats bool `ini:"public_stats"`
Monetization bool `ini:"monetization"`
NotesOnly bool `ini:"notes_only"`
// Access
Private bool `ini:"private"`
@ -128,6 +165,14 @@ type (
// Check for Updates
UpdateChecks bool `ini:"update_checks"`
// Disable password authentication if use only Oauth
DisablePasswordAuth bool `ini:"disable_password_auth"`
}
EmailCfg struct {
Domain string `ini:"domain"`
MailgunPrivate string `ini:"mailgun_private"`
}
// Config holds the complete configuration for running a writefreely instance
@ -135,9 +180,12 @@ type (
Server ServerCfg `ini:"server"`
Database DatabaseCfg `ini:"database"`
App AppCfg `ini:"app"`
Email EmailCfg `ini:"email"`
SlackOauth SlackOauthCfg `ini:"oauth.slack"`
WriteAsOauth WriteAsOauthCfg `ini:"oauth.writeas"`
GitlabOauth GitlabOauthCfg `ini:"oauth.gitlab"`
GiteaOauth GiteaOauthCfg `ini:"oauth.gitea"`
GenericOauth GenericOauthCfg `ini:"oauth.generic"`
}
)
@ -193,6 +241,10 @@ func (ac *AppCfg) LandingPath() string {
return ac.Landing
}
func (lc EmailCfg) Enabled() bool {
return lc.Domain != "" && lc.MailgunPrivate != ""
}
func (ac AppCfg) SignupPath() string {
if !ac.OpenRegistration {
return ""
@ -219,6 +271,22 @@ func Load(fname string) (*Config, error) {
if err != nil {
return nil, err
}
// Do any transformations
u, err := url.Parse(uc.App.Host)
if err != nil {
return nil, err
}
d, err := idna.ToASCII(u.Hostname())
if err != nil {
log.Error("idna.ToASCII for %s: %s", u.Hostname(), err)
return nil, err
}
uc.App.Host = u.Scheme + "://" + d
if u.Port() != "" {
uc.App.Host += ":" + u.Port()
}
return uc, nil
}

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018 A Bunch Tell LLC.
* Copyright © 2018 Musing Studio LLC.
*
* This file is part of WriteFreely.
*

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018 A Bunch Tell LLC.
* Copyright © 2018, 2020-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -11,14 +11,34 @@
package config
import (
"github.com/writeas/web-core/log"
"golang.org/x/net/idna"
"net/http"
"net/url"
"strings"
"time"
)
// FriendlyHost returns the app's Host sans any schema
func (ac AppCfg) FriendlyHost() string {
return ac.Host[strings.Index(ac.Host, "://")+len("://"):]
rawHost := ac.Host[strings.Index(ac.Host, "://")+len("://"):]
u, err := url.Parse(ac.Host)
if err != nil {
log.Error("url.Parse failed on %s: %s", ac.Host, err)
return rawHost
}
d, err := idna.ToUnicode(u.Hostname())
if err != nil {
log.Error("idna.ToUnicode failed on %s: %s", ac.Host, err)
return rawHost
}
res := d
if u.Port() != "" {
res += ":" + u.Port()
}
return res
}
func (ac AppCfg) CanCreateBlogs(currentlyUsed uint64) bool {

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018 A Bunch Tell LLC.
* Copyright © 2018 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -57,7 +57,7 @@ func Configure(fname string, configSections string) (*SetupData, error) {
Success: "{{ . | bold | faint }}: ",
}
selTmpls := &promptui.SelectTemplates{
Selected: fmt.Sprintf(`{{.Label}} {{ . | faint }}`),
Selected: `{{.Label}} {{ . | faint }}`,
}
var selPrompt promptui.Select
@ -356,7 +356,7 @@ func Configure(fname string, configSections string) (*SetupData, error) {
if data.Config.App.Federation {
selPrompt = promptui.Select{
Templates: selTmpls,
Label: "Federation usage stats",
Label: "Usage stats (active users, posts)",
Items: []string{"Public", "Private"},
}
_, fedStatsType, err := selPrompt.Run()

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018 A Bunch Tell LLC.
* Copyright © 2018 Musing Studio LLC.
*
* This file is part of WriteFreely.
*

View File

@ -1,7 +1,8 @@
//go:build wflib
// +build wflib
/*
* Copyright © 2019-2020 A Bunch Tell LLC.
* Copyright © 2019-2020 Musing Studio LLC.
*
* This file is part of WriteFreely.
*

View File

@ -1,7 +1,8 @@
//go:build !sqlite && !wflib
// +build !sqlite,!wflib
/*
* Copyright © 2019-2020 A Bunch Tell LLC.
* Copyright © 2019-2020 Musing Studio LLC.
*
* This file is part of WriteFreely.
*

View File

@ -1,7 +1,8 @@
//go:build sqlite && !wflib
// +build sqlite,!wflib
/*
* Copyright © 2019-2020 A Bunch Tell LLC.
* Copyright © 2019-2020 Musing Studio LLC.
*
* This file is part of WriteFreely.
*

File diff suppressed because it is too large Load Diff

View File

@ -18,13 +18,13 @@ func TestOAuthDatastore(t *testing.T) {
driverName: "",
}
state, err := ds.GenerateOAuthState(ctx, "test", "development", 0)
state, err := ds.GenerateOAuthState(ctx, "test", "development", 0, "")
assert.NoError(t, err)
assert.Len(t, state, 24)
countRows(t, ctx, db, 1, "SELECT COUNT(*) FROM `oauth_client_states` WHERE `state` = ? AND `used` = false", state)
_, _, _, err = ds.ValidateOAuthState(ctx, state)
_, _, _, _, err = ds.ValidateOAuthState(ctx, state)
assert.NoError(t, err)
countRows(t, ctx, db, 1, "SELECT COUNT(*) FROM `oauth_client_states` WHERE `state` = ? AND `used` = true", state)

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2019-2020 A Bunch Tell LLC.
* Copyright © 2019-2020 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -247,10 +247,7 @@ func (b *CreateTableSqlBuilder) ToSQL() (string, error) {
}
things = append(things, columnStr)
}
for _, constraint := range b.Constraints {
things = append(things, constraint)
}
things = append(things, b.Constraints...)
if thingLen := len(things); thingLen > 0 {
str.WriteString(" ( ")
for i, thing := range things {

View File

@ -23,4 +23,3 @@ func RunTransactionWithOptions(ctx context.Context, db *sql.DB, txOpts *sql.TxOp
}
return tx.Commit()
}

View File

@ -1,32 +1,47 @@
version: "3"
services:
web:
build: .
volumes:
- "web-data:/go/src/app"
- "./config.ini.example:/go/src/app/config.ini"
ports:
- "8080:8080"
networks:
- writefreely
depends_on:
- db
restart: unless-stopped
db:
image: "mariadb:latest"
volumes:
- "./schema.sql:/tmp/schema.sql"
- db-data:/var/lib/mysql/data
networks:
- writefreely
environment:
- MYSQL_DATABASE=writefreely
- MYSQL_ROOT_PASSWORD=changeme
restart: unless-stopped
volumes:
web-data:
web-keys:
db-data:
networks:
writefreely:
external_writefreely:
internal_writefreely:
internal: true
services:
writefreely-web:
container_name: "writefreely-web"
image: "writeas/writefreely:latest"
volumes:
- "web-keys:/go/keys"
- "./config.ini:/go/config.ini"
networks:
- "internal_writefreely"
- "external_writefreely"
ports:
- "8080:8080"
depends_on:
- "writefreely-db"
restart: unless-stopped
writefreely-db:
container_name: "writefreely-db"
image: "mariadb:latest"
volumes:
- "db-data:/var/lib/mysql/data"
networks:
- "internal_writefreely"
environment:
- MYSQL_DATABASE=writefreely
- MYSQL_ROOT_PASSWORD=changeme
restart: unless-stopped

462
email.go Normal file
View File

@ -0,0 +1,462 @@
/*
* Copyright © 2019-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package writefreely
import (
"database/sql"
"encoding/json"
"fmt"
"html/template"
"net/http"
"strings"
"time"
"github.com/aymerick/douceur/inliner"
"github.com/gorilla/mux"
"github.com/mailgun/mailgun-go"
stripmd "github.com/writeas/go-strip-markdown/v2"
"github.com/writeas/impart"
"github.com/writeas/web-core/data"
"github.com/writeas/web-core/log"
"github.com/writefreely/writefreely/key"
"github.com/writefreely/writefreely/spam"
)
const (
emailSendDelay = 15
)
type (
SubmittedSubscription struct {
CollAlias string
UserID int64
Email string `schema:"email" json:"email"`
Web bool `schema:"web" json:"web"`
Slug string `schema:"slug" json:"slug"`
From string `schema:"from" json:"from"`
}
EmailSubscriber struct {
ID string
CollID int64
UserID sql.NullInt64
Email sql.NullString
Subscribed time.Time
Token string
Confirmed bool
AllowExport bool
acctEmail sql.NullString
}
)
func (es *EmailSubscriber) FinalEmail(keys *key.Keychain) string {
if !es.UserID.Valid || es.Email.Valid {
return es.Email.String
}
decEmail, err := data.Decrypt(keys.EmailKey, []byte(es.acctEmail.String))
if err != nil {
log.Error("Error decrypting user email: %v", err)
return ""
}
return string(decEmail)
}
func (es *EmailSubscriber) SubscribedFriendly() string {
return es.Subscribed.Format("January 2, 2006")
}
func handleCreateEmailSubscription(app *App, w http.ResponseWriter, r *http.Request) error {
reqJSON := IsJSON(r)
vars := mux.Vars(r)
var err error
ss := SubmittedSubscription{
CollAlias: vars["alias"],
}
u := getUserSession(app, r)
if u != nil {
ss.UserID = u.ID
}
if reqJSON {
// Decode JSON request
decoder := json.NewDecoder(r.Body)
err = decoder.Decode(&ss)
if err != nil {
log.Error("Couldn't parse new subscription JSON request: %v\n", err)
return ErrBadJSON
}
} else {
err = r.ParseForm()
if err != nil {
log.Error("Couldn't parse new subscription form request: %v\n", err)
return ErrBadFormData
}
err = app.formDecoder.Decode(&ss, r.PostForm)
if err != nil {
log.Error("Continuing, but error decoding new subscription form request: %v\n", err)
//return ErrBadFormData
}
}
c, err := app.db.GetCollection(ss.CollAlias)
if err != nil {
log.Error("getCollection: %s", err)
return err
}
c.hostName = app.cfg.App.Host
from := c.CanonicalURL()
isAuthorBanned, err := app.db.IsUserSilenced(c.OwnerID)
if isAuthorBanned {
log.Info("Author is silenced, so subscription is blocked.")
return impart.HTTPError{http.StatusFound, from}
}
if ss.Web {
if u != nil && u.ID == c.OwnerID {
from = "/" + c.Alias + "/"
}
from += ss.Slug
}
if r.FormValue(spam.HoneypotFieldName()) != "" || r.FormValue("fake_password") != "" {
log.Info("Honeypot field was filled out! Not subscribing.")
return impart.HTTPError{http.StatusFound, from}
}
if ss.Email == "" && ss.UserID < 1 {
log.Info("No subscriber data. Not subscribing.")
return impart.HTTPError{http.StatusFound, from}
}
confirmed := app.db.IsSubscriberConfirmed(ss.Email)
es, err := app.db.AddEmailSubscription(c.ID, ss.UserID, ss.Email, confirmed)
if err != nil {
log.Error("addEmailSubscription: %s", err)
return err
}
// Send confirmation email if needed
if !confirmed {
err = sendSubConfirmEmail(app, c, ss.Email, es.ID, es.Token)
if err != nil {
log.Error("Failed to send subscription confirmation email: %s", err)
return err
}
}
if ss.Web {
session, err := app.sessionStore.Get(r, userEmailCookieName)
if err != nil {
// The cookie should still save, even if there's an error.
// Source: https://github.com/gorilla/sessions/issues/16#issuecomment-143642144
log.Error("Getting user email cookie: %v; ignoring", err)
}
if confirmed {
addSessionFlash(app, w, r, "<strong>Subscribed</strong>. You'll now receive future blog posts via email.", nil)
} else {
addSessionFlash(app, w, r, "Please check your email and <strong>click the confirmation link</strong> to subscribe.", nil)
}
session.Values[userEmailCookieVal] = ss.Email
err = session.Save(r, w)
if err != nil {
log.Error("save email cookie: %s", err)
return err
}
return impart.HTTPError{http.StatusFound, from}
}
return impart.WriteSuccess(w, "", http.StatusAccepted)
}
func handleDeleteEmailSubscription(app *App, w http.ResponseWriter, r *http.Request) error {
alias := collectionAliasFromReq(r)
vars := mux.Vars(r)
subID := vars["subscriber"]
email := r.FormValue("email")
token := r.FormValue("t")
slug := r.FormValue("slug")
isWeb := r.Method == "GET"
// Display collection if this is a collection
var c *Collection
var err error
if app.cfg.App.SingleUser {
c, err = app.db.GetCollectionByID(1)
} else {
c, err = app.db.GetCollection(alias)
}
if err != nil {
log.Error("Get collection: %s", err)
return err
}
from := c.CanonicalURL()
if subID != "" {
// User unsubscribing via email, so assume action is taken by either current
// user or not current user, and only use the request's information to
// satisfy this unsubscribe, i.e. subscriberID and token.
err = app.db.DeleteEmailSubscriber(subID, token)
} else {
// User unsubscribing through the web app, so assume action is taken by
// currently-auth'd user.
var userID int64
u := getUserSession(app, r)
if u != nil {
// User is logged in
userID = u.ID
if userID == c.OwnerID {
from = "/" + c.Alias + "/"
}
}
if email == "" && userID <= 0 {
// Get email address from saved cookie
session, err := app.sessionStore.Get(r, userEmailCookieName)
if err != nil {
log.Error("Unable to get email cookie: %s", err)
} else {
email = session.Values[userEmailCookieVal].(string)
}
}
if email == "" && userID <= 0 {
err = fmt.Errorf("No subscriber given.")
log.Error("Not deleting subscription: %s", err)
return err
}
err = app.db.DeleteEmailSubscriberByUser(email, userID, c.ID)
}
if err != nil {
log.Error("Unable to delete subscriber: %v", err)
return err
}
if isWeb {
from += slug
addSessionFlash(app, w, r, "<strong>Unsubscribed</strong>. You will no longer receive these blog posts via email.", nil)
return impart.HTTPError{http.StatusFound, from}
}
return impart.WriteSuccess(w, "", http.StatusAccepted)
}
func handleConfirmEmailSubscription(app *App, w http.ResponseWriter, r *http.Request) error {
alias := collectionAliasFromReq(r)
subID := mux.Vars(r)["subscriber"]
token := r.FormValue("t")
var c *Collection
var err error
if app.cfg.App.SingleUser {
c, err = app.db.GetCollectionByID(1)
} else {
c, err = app.db.GetCollection(alias)
}
if err != nil {
log.Error("Get collection: %s", err)
return err
}
from := c.CanonicalURL()
err = app.db.UpdateSubscriberConfirmed(subID, token)
if err != nil {
addSessionFlash(app, w, r, err.Error(), nil)
return impart.HTTPError{http.StatusFound, from}
}
addSessionFlash(app, w, r, "<strong>Confirmed</strong>! Thanks. Now you'll receive future blog posts via email.", nil)
return impart.HTTPError{http.StatusFound, from}
}
func emailPost(app *App, p *PublicPost, collID int64) error {
p.augmentContent()
// Do some shortcode replacement.
// Since the user is receiving this email, we can assume they're subscribed via email.
p.Content = strings.Replace(p.Content, "<!--emailsub-->", `<p id="emailsub">You're subscribed to email updates.</p>`, -1)
if p.HTMLContent == template.HTML("") {
p.formatContent(app.cfg, false, false)
}
p.augmentReadingDestination()
title := p.Title.String
if title != "" {
title = p.Title.String + "\n\n"
}
plainMsg := title + "A new post from " + p.CanonicalURL(app.cfg.App.Host) + "\n\n" + stripmd.Strip(p.Content)
plainMsg += `
---------------------------------------------------------------------------------
Originally published on ` + p.Collection.DisplayTitle() + ` (` + p.Collection.CanonicalURL() + `), a blog you subscribe to.
Sent to %recipient.to%. Unsubscribe: ` + p.Collection.CanonicalURL() + `email/unsubscribe/%recipient.id%?t=%recipient.token%`
gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate)
m := mailgun.NewMessage(p.Collection.DisplayTitle()+" <"+p.Collection.Alias+"@"+app.cfg.Email.Domain+">", stripmd.Strip(p.DisplayTitle()), plainMsg)
replyTo := app.db.GetCollectionAttribute(collID, collAttrLetterReplyTo)
if replyTo != "" {
m.SetReplyTo(replyTo)
}
subs, err := app.db.GetEmailSubscribers(collID, true)
if err != nil {
log.Error("Unable to get email subscribers: %v", err)
return err
}
if len(subs) == 0 {
return nil
}
if title != "" {
title = string(`<h2 id="title">` + p.FormattedDisplayTitle() + `</h2>`)
}
m.AddTag("New post")
fontFam := "Lora, Palatino, Baskerville, serif"
if p.IsSans() {
fontFam = `"Open Sans", Tahoma, Arial, sans-serif`
} else if p.IsMonospace() {
fontFam = `Hack, consolas, Menlo-Regular, Menlo, Monaco, monospace, monospace`
}
// TODO: move this to a templated file and LESS-generated stylesheet
fullHTML := `<html>
<head>
<style>
body {
font-size: 120%;
font-family: ` + fontFam + `;
margin: 1em 2em;
}
#article {
line-height: 1.5;
margin: 1.5em 0;
white-space: pre-wrap;
word-wrap: break-word;
}
h1, h2, h3, h4, h5, h6, p, code {
display: inline
}
img, iframe, video {
max-width: 100%
}
#title {
margin-bottom: 1em;
display: block;
}
.intro {
font-style: italic;
font-size: 0.95em;
}
div#footer {
text-align: center;
max-width: 35em;
margin: 2em auto;
}
div#footer p {
display: block;
font-size: 0.86em;
color: #666;
}
hr {
border: 1px solid #ccc;
margin: 2em 1em;
}
p#emailsub {
text-align: center;
display: inline-block !important;
width: 100%;
font-style: italic;
}
</style>
</head>
<body>
<div id="article">` + title + `<p class="intro">From <a href="` + p.CanonicalURL(app.cfg.App.Host) + `">` + p.DisplayCanonicalURL() + `</a></p>
` + string(p.HTMLContent) + `</div>
<hr />
<div id="footer">
<p>Originally published on <a href="` + p.Collection.CanonicalURL() + `">` + p.Collection.DisplayTitle() + `</a>, a blog you subscribe to.</p>
<p>Sent to %recipient.to%. <a href="` + p.Collection.CanonicalURL() + `email/unsubscribe/%recipient.id%?t=%recipient.token%">Unsubscribe</a>.</p>
</div>
</body>
</html>`
// inline CSS
html, err := inliner.Inline(fullHTML)
if err != nil {
log.Error("Unable to inline email HTML: %v", err)
return err
}
m.SetHtml(html)
log.Info("[email] Adding %d recipient(s)", len(subs))
for _, s := range subs {
e := s.FinalEmail(app.keys)
log.Info("[email] Adding %s", e)
err = m.AddRecipientAndVariables(e, map[string]interface{}{
"id": s.ID,
"to": e,
"token": s.Token,
})
if err != nil {
log.Error("Unable to add receipient %s: %s", e, err)
}
}
res, _, err := gun.Send(m)
log.Info("[email] Send result: %s", res)
if err != nil {
log.Error("Unable to send post email: %v", err)
return err
}
return nil
}
func sendSubConfirmEmail(app *App, c *Collection, email, subID, token string) error {
if email == "" {
return fmt.Errorf("You must supply an email to verify.")
}
// Send email
gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate)
plainMsg := "Confirm your subscription to " + c.DisplayTitle() + ` (` + c.CanonicalURL() + `) to start receiving future posts. Simply click the following link (or copy and paste it into your browser):
` + c.CanonicalURL() + "email/confirm/" + subID + "?t=" + token + `
If you didn't subscribe to this site or you're not sure why you're getting this email, you can delete it. You won't be subscribed or receive any future emails.`
m := mailgun.NewMessage(c.DisplayTitle()+" <"+c.Alias+"@"+app.cfg.Email.Domain+">", "Confirm your subscription to "+c.DisplayTitle(), plainMsg, fmt.Sprintf("<%s>", email))
m.AddTag("Email Verification")
m.SetHtml(`<html>
<body style="font-family:Lora, 'Palatino Linotype', Palatino, Baskerville, 'Book Antiqua', 'New York', 'DejaVu serif', serif; font-size: 100%%; margin:1em 2em;">
<div style="font-size: 1.2em;">
<p>Confirm your subscription to <a href="` + c.CanonicalURL() + `">` + c.DisplayTitle() + `</a> to start receiving future posts:</p>
<p><a href="` + c.CanonicalURL() + `email/confirm/` + subID + `?t=` + token + `">Subscribe to ` + c.DisplayTitle() + `</a></p>
<p>If you didn't subscribe to this site or you're not sure why you're getting this email, you can delete it. You won't be subscribed or receive any future emails.</p>
</div>
</body>
</html>`)
gun.Send(m)
return nil
}

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018-2020 A Bunch Tell LLC.
* Copyright © 2018-2020 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -52,6 +52,8 @@ var (
ErrUserNotFoundEmail = impart.HTTPError{http.StatusNotFound, "Please enter your username instead of your email address."}
ErrUserSilenced = impart.HTTPError{http.StatusForbidden, "Account is silenced."}
ErrDisabledPasswordAuth = impart.HTTPError{http.StatusForbidden, "Password authentication is disabled."}
)
// Post operation errors

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018-2019 A Bunch Tell LLC.
* Copyright © 2018-2019 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -110,7 +110,7 @@ func compileFullExport(app *App, u *User) *ExportUser {
log.Error("unable to fetch collections: %v", err)
}
posts, err := app.db.GetAnonymousPosts(u)
posts, err := app.db.GetAnonymousPosts(u, 0)
if err != nil {
log.Error("unable to fetch anon posts: %v", err)
}

24
feed.go
View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018-2019 A Bunch Tell LLC.
* Copyright © 2018-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -15,9 +15,9 @@ import (
"net/http"
"time"
. "github.com/gorilla/feeds"
"github.com/gorilla/feeds"
"github.com/gorilla/mux"
stripmd "github.com/writeas/go-strip-markdown"
stripmd "github.com/writeas/go-strip-markdown/v2"
"github.com/writeas/web-core/log"
)
@ -87,25 +87,29 @@ func ViewFeed(app *App, w http.ResponseWriter, req *http.Request) error {
siteURL += "tag:" + tag
}
feed := &Feed{
feed := &feeds.Feed{
Title: collectionTitle,
Link: &Link{Href: siteURL},
Link: &feeds.Link{Href: siteURL},
Description: coll.Description,
Author: &Author{author, ""},
Author: &feeds.Author{author, ""},
Created: time.Now(),
}
var title, permalink string
for _, p := range *coll.Posts {
// Add necessary path back to the web browser for Web Monetization if needed
p.Collection = coll.CollectionObj // augmentReadingDestination requires a populated Collection field
p.augmentReadingDestination()
// Create the item for the feed
title = p.PlainDisplayTitle()
permalink = fmt.Sprintf("%s%s", baseUrl, p.Slug.String)
feed.Items = append(feed.Items, &Item{
feed.Items = append(feed.Items, &feeds.Item{
Id: fmt.Sprintf("%s%s", basePermalinkUrl, p.Slug.String),
Title: title,
Link: &Link{Href: permalink},
Link: &feeds.Link{Href: permalink},
Description: "<![CDATA[" + stripmd.Strip(p.Content) + "]]>",
Content: applyMarkdown([]byte(p.Content), "", app.cfg),
Author: &Author{author, ""},
Content: string(p.HTMLContent),
Author: &feeds.Author{author, ""},
Created: p.Created,
Updated: p.Updated,
})

123
go.mod
View File

@ -1,65 +1,92 @@
module github.com/writeas/writefreely
module github.com/writefreely/writefreely
require (
github.com/alecthomas/gometalinter v3.0.0+incompatible // indirect
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect
github.com/captncraig/cors v0.0.0-20180620154129-376d45073b49 // indirect
github.com/PuerkitoBio/goquery v1.8.1 // indirect
github.com/aymerick/douceur v0.2.0
github.com/clbanning/mxj v1.8.4 // indirect
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9 // indirect
github.com/dustin/go-humanize v1.0.0
github.com/fatih/color v1.7.0
github.com/go-fed/httpsig v0.1.1-0.20190924171022-f4c36041199d // indirect
github.com/go-sql-driver/mysql v1.4.1
github.com/dustin/go-humanize v1.0.1
github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect
github.com/fatih/color v1.16.0
github.com/go-ini/ini v1.67.0
github.com/go-sql-driver/mysql v1.7.1
github.com/go-test/deep v1.0.1 // indirect
github.com/golang/lint v0.0.0-20181217174547-8f45f776aaf1 // indirect
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 // indirect
github.com/gobuffalo/envy v1.9.0 // indirect
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/gorilla/feeds v1.1.0
github.com/gorilla/mux v1.7.0
github.com/gorilla/schema v1.0.2
github.com/gorilla/sessions v1.2.0
github.com/guregu/null v3.4.0+incompatible
github.com/hashicorp/go-multierror v1.0.0
github.com/gorilla/csrf v1.7.2
github.com/gorilla/feeds v1.1.2
github.com/gorilla/mux v1.8.1
github.com/gorilla/schema v1.2.1
github.com/gorilla/sessions v1.2.2
github.com/guregu/null v4.0.0+incompatible
github.com/hashicorp/go-multierror v1.1.1
github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2
github.com/jteeuwen/go-bindata v3.0.7+incompatible // indirect
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec
github.com/lunixbochs/vtclean v1.0.0 // indirect
github.com/manifoldco/promptui v0.3.2
github.com/mattn/go-colorable v0.1.0 // indirect
github.com/mattn/go-sqlite3 v1.10.0
github.com/microcosm-cc/bluemonday v1.0.2
github.com/mitchellh/go-wordwrap v1.0.0
github.com/nicksnyder/go-i18n v1.10.0 // indirect
github.com/mailgun/mailgun-go v2.0.0+incompatible
github.com/manifoldco/promptui v0.9.0
github.com/mattn/go-sqlite3 v1.14.21
github.com/microcosm-cc/bluemonday v1.0.26
github.com/mitchellh/go-wordwrap v1.0.1
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d
github.com/pelletier/go-toml v1.2.0 // indirect
github.com/pkg/errors v0.8.1 // indirect
github.com/onsi/ginkgo v1.16.4 // indirect
github.com/onsi/gomega v1.13.0 // indirect
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
github.com/stretchr/testify v1.3.0
github.com/urfave/cli/v2 v2.1.1
github.com/stretchr/testify v1.9.0
github.com/urfave/cli/v2 v2.27.1
github.com/writeas/activity v0.1.2
github.com/writeas/activityserve v0.0.0-20191115095800-dd6d19cc8b89
github.com/writeas/go-strip-markdown v2.0.1+incompatible
github.com/writeas/go-webfinger v0.0.0-20190106002315-85cf805c86d2
github.com/writeas/activityserve v0.0.0-20230428180247-dc13a4f4d835
github.com/writeas/go-strip-markdown/v2 v2.1.1
github.com/writeas/go-webfinger v1.1.0
github.com/writeas/httpsig v1.0.0
github.com/writeas/impart v1.1.1-0.20191230230525-d3c45ced010d
github.com/writeas/import v0.2.0
github.com/writeas/monday v0.0.0-20181024183321-54a7dd579219
github.com/writeas/nerds v1.0.0
github.com/writeas/saturday v1.7.1
github.com/writeas/impart v1.1.1
github.com/writeas/import v0.2.1
github.com/writeas/monday v1.3.0
github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320
github.com/writeas/slug v1.2.0
github.com/writeas/web-core v1.2.0
github.com/writeas/web-core v1.6.1-0.20231003013047-d81124d45431
github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b
github.com/writefreely/go-nodeinfo v1.2.0
golang.org/dl v0.0.0-20200319204010-bf12898a6070 // indirect
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17
golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1 // indirect
golang.org/x/tools v0.0.0-20190208222737-3744606dbb67 // indirect
google.golang.org/appengine v1.4.0 // indirect
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20180810215634-df19058c872c // indirect
gopkg.in/ini.v1 v1.41.0
src.techknowlogick.com/xgo v0.0.0-20200129005940-d0fae26e014b // indirect
golang.org/x/crypto v0.21.0
golang.org/x/net v0.22.0
)
go 1.13
require (
code.as/core/socks v1.0.0 // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/beevik/etree v1.1.0 // indirect
github.com/captncraig/cors v0.0.0-20190703115713-e80254a89df1 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe // indirect
github.com/gofrs/uuid v3.3.0+incompatible // indirect
github.com/gologme/log v1.2.0 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/joho/godotenv v1.3.0 // indirect
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/writeas/go-writeas/v2 v2.0.2 // indirect
github.com/writeas/openssl-go v1.0.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
go 1.19

373
go.sum
View File

@ -1,16 +1,16 @@
code.as/core/socks v1.0.0 h1:SPQXNp4SbEwjOAP9VzUahLHak8SDqy5n+9cm9tpjZOs=
code.as/core/socks v1.0.0/go.mod h1:BAXBy5O9s2gmw6UxLqNJcVbWY7C/UPs+801CcSsfWOY=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/alecthomas/gometalinter v2.0.11+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk=
github.com/alecthomas/gometalinter v3.0.0+incompatible h1:e9Zfvfytsw/e6Kd/PYd75wggK+/kX5Xn8IYDUKyc5fU=
github.com/alecthomas/gometalinter v3.0.0+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/captncraig/cors v0.0.0-20180620154129-376d45073b49 h1:jWNY1NDg6a/c8RSXkai7IX6UOhir0LD39I4Dukg+4Ks=
github.com/captncraig/cors v0.0.0-20180620154129-376d45073b49/go.mod h1:EIlIeMufZ8nqdUhnesledB15xLRl4wIJUppwDLPrdrQ=
github.com/captncraig/cors v0.0.0-20190703115713-e80254a89df1 h1:AFSJaASPGYNbkUa5c8ZybrcW9pP3Cy7+z5dnpcc/qG8=
github.com/captncraig/cors v0.0.0-20190703115713-e80254a89df1/go.mod h1:EIlIeMufZ8nqdUhnesledB15xLRl4wIJUppwDLPrdrQ=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
@ -20,104 +20,142 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/clbanning/mxj v1.8.3/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9 h1:74lLNRzvsdIlkTgfDSMuaPjBr4cf6k7pwQQANm/yLKU=
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs=
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0=
github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk=
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/go-fed/httpsig v0.1.0 h1:6F2OxRVnNTN4OPN+Mc2jxs2WEay9/qiHT/jphlvAwIY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-fed/httpsig v0.1.0/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE=
github.com/go-fed/httpsig v0.1.1-0.20190924171022-f4c36041199d h1:+uoOvOnNDgsYbWtAij4xP6Rgir3eJGjocFPxBJETU/U=
github.com/go-fed/httpsig v0.1.1-0.20190924171022-f4c36041199d/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe h1:U71giCx5NjRn4Lb71UuprPHqhjxGv3Jqonb9fgcaJH8=
github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg=
github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/lint v0.0.0-20181217174547-8f45f776aaf1 h1:6DVPu65tee05kY0/rciBQ47ue+AnuY8KTayV6VHikIo=
github.com/golang/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/gobuffalo/envy v1.9.0 h1:eZR0DuEgVLfeIb1zIKt3bT4YovIMf9O9LXQeCZLXpqE=
github.com/gobuffalo/envy v1.9.0/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTusVSAi0WdXTpfNVGY2aHycNKY=
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U=
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/gologme/log v1.2.0 h1:Ya5Ip/KD6FX7uH0S31QO87nCCSucKtF44TLbTtO7V4c=
github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc h1:cJlkeAx1QYgO5N80aF5xRGstVsRQwgLR7uA2FnP1ZjY=
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
github.com/gorilla/feeds v1.1.0 h1:pcgLJhbdYgaUESnj3AmXPcB7cS3vy63+jC/TI14AGXk=
github.com/gorilla/feeds v1.1.0/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/schema v1.0.2 h1:sAgNfOcNYvdDSrzGHVy9nzCQahG+qmsg+nE8dK85QRA=
github.com/gorilla/schema v1.0.2/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/guregu/null v3.4.0+incompatible h1:a4mw37gBO7ypcBlTJeZGuMpSxxFTV9qFfFKgWxQSGaM=
github.com/guregu/null v3.4.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM=
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/gorilla/feeds v1.1.2 h1:pxzZ5PD3RJdhFH2FsJJ4x6PqMqbgFk1+Vez4XWBW8Iw=
github.com/gorilla/feeds v1.1.2/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/schema v1.2.1 h1:tjDxcmdb+siIqkTNoV+qRH2mjYdr2hHe5MKXbp61ziM=
github.com/gorilla/schema v1.2.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/guregu/null v4.0.0+incompatible h1:4zw0ckM7ECd6FNNddc3Fu4aty9nTlpkkzH7dPn4/4Gw=
github.com/guregu/null v4.0.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2 h1:wIdDEle9HEy7vBPjC6oKz6ejs3Ut+jmsYvuOoAW2pSM=
github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2/go.mod h1:WtaVKD9TeruTED9ydiaOJU08qGoEPP/LyzTKiD3jEsw=
github.com/jteeuwen/go-bindata v3.0.7+incompatible h1:91Uy4d9SYVr1kyTJ15wJsog+esAZZl7JmEfTkwmhJts=
github.com/jteeuwen/go-bindata v3.0.7+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec h1:ZXWuspqypleMuJy4bzYEqlMhJnGAYpLrWe5p7W3CdvI=
github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec/go.mod h1:voECJzdraJmolzPBgL9Z7ANwXf4oMXaTCsIkdiPpR/g=
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/manifoldco/promptui v0.3.2 h1:rir7oByTERac6jhpHUPErHuopoRDvO3jxS+FdadEns8=
github.com/manifoldco/promptui v0.3.2/go.mod h1:8JU+igZ+eeiiRku4T5BjtKh2ms8sziGpSYl1gN8Bazw=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.0 h1:v2XXALHHh6zHfYTJ+cSkwtyffnaOyR1MXaA91mTrb8o=
github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/nicksnyder/go-i18n v1.10.0 h1:5AzlPKvXBH4qBzmZ09Ua9Gipyruv6uApMcrNZdo96+Q=
github.com/nicksnyder/go-i18n v1.10.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q=
github.com/mailgun/mailgun-go v2.0.0+incompatible h1:0FoRHWwMUctnd8KIR3vtZbqdfjpIMxOZgcSa51s8F8o=
github.com/mailgun/mailgun-go v2.0.0+incompatible/go.mod h1:NWTyU+O4aczg/nsGhQnvHL6v2n5Gy6Sv5tNDVvC6FbU=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.21 h1:IXocQLOykluc3xPE0Lvy8FtggMz1G+U3mEjg+0zGizc=
github.com/mattn/go-sqlite3 v1.14.21/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4=
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
@ -125,86 +163,157 @@ github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9 h1:vY5WqiEon0ZSTGM3ayVVi+twaHKHDFUVloaQ/wug9/c=
github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYvZ+fpjMXqs+XEriussHjSYqeXVnAdSV1tkMYk=
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/writeas/activity v0.1.2 h1:Y12B5lIrabfqKE7e7HFCWiXrlfXljr9tlkFm2mp7DgY=
github.com/writeas/activity v0.1.2/go.mod h1:mYYgiewmEM+8tlifirK/vl6tmB2EbjYaxwb+ndUw5T0=
github.com/writeas/activityserve v0.0.0-20191115095800-dd6d19cc8b89 h1:NJhzq9aTccL3SSSZMrcnYhkD6sObdY9otNZ1X6/ZKNE=
github.com/writeas/activityserve v0.0.0-20191115095800-dd6d19cc8b89/go.mod h1:Kz62mzYsCnrFTSTSFLXFj3fGYBQOntmBWTDDq57b46A=
github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw=
github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE=
github.com/writeas/go-webfinger v0.0.0-20190106002315-85cf805c86d2 h1:DUsp4OhdfI+e6iUqcPQlwx8QYXuUDsToTz/x82D3Zuo=
github.com/writeas/go-webfinger v0.0.0-20190106002315-85cf805c86d2/go.mod h1:w2VxyRO/J5vfNjJHYVubsjUGHd3RLDoVciz0DE3ApOc=
github.com/writeas/go-writeas v1.1.0 h1:WHGm6wriBkxYAOGbvriXH8DlMUGOi6jhSZLUZKQ+4mQ=
github.com/writeas/activityserve v0.0.0-20230428180247-dc13a4f4d835 h1:bm/7gYo6y3GxtTa1qyUFyCk29CTnBAKt7z4D2MASYrw=
github.com/writeas/activityserve v0.0.0-20230428180247-dc13a4f4d835/go.mod h1:4akDJSl+sSp+QhrQKMqzAqdV1gJ1pPx6XPI77zgMM8o=
github.com/writeas/go-strip-markdown/v2 v2.1.1 h1:hAxUM21Uhznf/FnbVGiJciqzska6iLei22Ijc3q2e28=
github.com/writeas/go-strip-markdown/v2 v2.1.1/go.mod h1:UvvgPJgn1vvN8nWuE5e7v/+qmDu3BSVnKAB6Gl7hFzA=
github.com/writeas/go-webfinger v1.1.0 h1:MzNyt0ry/GMsRmJGftn2o9mPwqK1Q5MLdh4VuJCfb1Q=
github.com/writeas/go-webfinger v1.1.0/go.mod h1:w2VxyRO/J5vfNjJHYVubsjUGHd3RLDoVciz0DE3ApOc=
github.com/writeas/go-writeas v1.1.0/go.mod h1:oh9U1rWaiE0p3kzdKwwvOpNXgp0P0IELI7OLOwV4fkA=
github.com/writeas/go-writeas/v2 v2.0.2 h1:akvdMg89U5oBJiCkBwOXljVLTqP354uN6qnG2oOMrbk=
github.com/writeas/go-writeas/v2 v2.0.2/go.mod h1:9sjczQJKmru925fLzg0usrU1R1tE4vBmQtGnItUMR0M=
github.com/writeas/httpsig v1.0.0 h1:peIAoIA3DmlP8IG8tMNZqI4YD1uEnWBmkcC9OFPjt3A=
github.com/writeas/httpsig v1.0.0/go.mod h1:7ClMGSrSVXJbmiLa17bZ1LrG1oibGZmUMlh3402flPY=
github.com/writeas/impart v1.1.0 h1:nPnoO211VscNkp/gnzir5UwCDEvdHThL5uELU60NFSE=
github.com/writeas/impart v1.1.0/go.mod h1:g0MpxdnTOHHrl+Ca/2oMXUHJ0PcRAEWtkCzYCJUXC9Y=
github.com/writeas/impart v1.1.1-0.20191230230525-d3c45ced010d h1:PK7DOj3JE6MGf647esPrKzXEHFjGWX2hl22uX79ixaE=
github.com/writeas/impart v1.1.1-0.20191230230525-d3c45ced010d/go.mod h1:g0MpxdnTOHHrl+Ca/2oMXUHJ0PcRAEWtkCzYCJUXC9Y=
github.com/writeas/import v0.2.0 h1:Ov23JW9Rnjxk06rki1Spar45bNX647HhwhAZj3flJiY=
github.com/writeas/import v0.2.0/go.mod h1:gFe0Pl7ZWYiXbI0TJxeMMyylPGZmhVvCfQxhMEc8CxM=
github.com/writeas/monday v0.0.0-20181024183321-54a7dd579219 h1:baEp0631C8sT2r/hqwypIw2snCFZa6h7U6TojoLHu/c=
github.com/writeas/monday v0.0.0-20181024183321-54a7dd579219/go.mod h1:NyM35ayknT7lzO6O/1JpfgGyv+0W9Z9q7aE0J8bXxfQ=
github.com/writeas/nerds v1.0.0 h1:ZzRcCN+Sr3MWID7o/x1cr1ZbLvdpej9Y1/Ho+JKlqxo=
github.com/writeas/nerds v1.0.0/go.mod h1:Gn2bHy1EwRcpXeB7ZhVmuUwiweK0e+JllNf66gvNLdU=
github.com/writeas/impart v1.1.1 h1:RyA9+CqbdbDuz53k+nXCWUY+NlEkdyw6+nWanxSBl5o=
github.com/writeas/impart v1.1.1/go.mod h1:g0MpxdnTOHHrl+Ca/2oMXUHJ0PcRAEWtkCzYCJUXC9Y=
github.com/writeas/import v0.2.1 h1:3k+bDNCyqaWdZinyUZtEO4je3mR6fr/nE4ozTh9/9Wg=
github.com/writeas/import v0.2.1/go.mod h1:gFe0Pl7ZWYiXbI0TJxeMMyylPGZmhVvCfQxhMEc8CxM=
github.com/writeas/monday v1.3.0 h1:h51wJ0DULXIDZ1w11zutLL7YCBRO5LznXISSzqVLZeA=
github.com/writeas/monday v1.3.0/go.mod h1:9/CdGLDdIeAvzvf4oeihX++PE/qXUT2+tUlPQKCfRWY=
github.com/writeas/openssl-go v1.0.0 h1:YXM1tDXeYOlTyJjoMlYLQH1xOloUimSR1WMF8kjFc5o=
github.com/writeas/openssl-go v1.0.0/go.mod h1:WsKeK5jYl0B5y8ggOmtVjbmb+3rEGqSD25TppjJnETA=
github.com/writeas/saturday v1.6.0/go.mod h1:ETE1EK6ogxptJpAgUbcJD0prAtX48bSloie80+tvnzQ=
github.com/writeas/saturday v1.7.1 h1:lYo1EH6CYyrFObQoA9RNWHVlpZA5iYL5Opxo7PYAnZE=
github.com/writeas/saturday v1.7.1/go.mod h1:ETE1EK6ogxptJpAgUbcJD0prAtX48bSloie80+tvnzQ=
github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320 h1:PozPZ29CQ/xt6ym/+FvIz+KvKEObSSc5ye+95zbTjVU=
github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320/go.mod h1:ETE1EK6ogxptJpAgUbcJD0prAtX48bSloie80+tvnzQ=
github.com/writeas/slug v1.2.0 h1:EMQ+cwLiOcA6EtFwUgyw3Ge18x9uflUnOnR6bp/J+/g=
github.com/writeas/slug v1.2.0/go.mod h1:RE8shOqQP3YhsfsQe0L3RnuejfQ4Mk+JjY5YJQFubfQ=
github.com/writeas/web-core v1.2.0 h1:CYqvBd+byi1cK4mCr1NZ6CjILuMOFmiFecv+OACcmG0=
github.com/writeas/web-core v1.2.0/go.mod h1:vTYajviuNBAxjctPp2NUYdgjofywVkxUGpeaERF3SfI=
github.com/writeas/web-core v1.6.1-0.20231003013047-d81124d45431 h1:ruqL2u87k504PXkR/fC4DcfZyyHmCindlpjOQKmyOsY=
github.com/writeas/web-core v1.6.1-0.20231003013047-d81124d45431/go.mod h1:7+idL4Y4woF7MnUfNX2mvkaQ8nLIJXths2y5iYPtA3k=
github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b h1:h3NzB8OZ50NNi5k9yrFeyFszt3LyqyVK4+xUHFYY8B0=
github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b/go.mod h1:T2UVVzt+R5KSSZe2xRSytnwc2M9AoDegi7foeIsik+M=
github.com/writefreely/go-nodeinfo v1.2.0 h1:La+YbTCvmpTwFhBSlebWDDL81N88Qf/SCAvRLR7F8ss=
github.com/writefreely/go-nodeinfo v1.2.0/go.mod h1:UTvE78KpcjYOlRHupZIiSEFcXHioTXuacCbHU+CAcPg=
golang.org/dl v0.0.0-20200319204010-bf12898a6070 h1:m3RoSUFYtel4F/gCw0tosY5Exe7hm2NbeNv/737FbSo=
golang.org/dl v0.0.0-20200319204010-bf12898a6070/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59 h1:hk3yo72LXLapY9EXVttc3Z1rLOxT9IuAPPX3GpY2+jo=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17 h1:nVJ3guKA9qdkEQ3TUdXI9QSINo2CUPM/cySEvw2w8I0=
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1 h1:rJm0LuqUjoDhSk2zO9ISMSToQxGz7Os2jRiOL8AWu4c=
golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180525142821-c11f84a56e43/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20181122213734-04b5d21e00f1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190208222737-3744606dbb67 h1:bPP/rGuN1LUM0eaEwo6vnP6OfIWJzJBulzGUiKLjjSY=
golang.org/x/tools v0.0.0-20190208222737-3744606dbb67/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20180810215634-df19058c872c h1:vTxShRUnK60yd8DZU+f95p1zSLj814+5CuEh7NjF2/Y=
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20180810215634-df19058c872c/go.mod h1:3HH7i1SgMqlzxCcBmUHW657sD4Kvv9sC3HpL3YukzwA=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.41.0 h1:Ka3ViY6gNYSKiVy71zXBEqKplnV35ImDLVG+8uoIklE=
gopkg.in/ini.v1 v1.41.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
src.techknowlogick.com/xgo v0.0.0-20200129005940-d0fae26e014b h1:rPAdjgXks4ToezTjygsnKZroxKVnA1L35DSpsJXPtfc=
src.techknowlogick.com/xgo v0.0.0-20200129005940-d0fae26e014b/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

167
gopher.go Normal file
View File

@ -0,0 +1,167 @@
/*
* Copyright © 2020 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package writefreely
import (
"bytes"
"fmt"
"io"
"net/url"
"regexp"
"strings"
"github.com/writeas/web-core/log"
"github.com/writefreely/go-gopher"
)
func initGopher(apper Apper) {
handler := NewWFHandler(apper)
gopher.HandleFunc("/", handler.Gopher(handleGopher))
log.Info("Serving on gopher://localhost:%d", apper.App().Config().Server.GopherPort)
gopher.ListenAndServe(fmt.Sprintf(":%d", apper.App().Config().Server.GopherPort), nil)
}
// Utility function to strip the URL from the hostname provided by app.cfg.App.Host
func stripHostProtocol(app *App) string {
u, err := url.Parse(app.cfg.App.Host)
if err != nil {
// Fall back to host, with scheme stripped
return string(regexp.MustCompile("^.*://").ReplaceAll([]byte(app.cfg.App.Host), []byte("")))
}
return u.Hostname()
}
func handleGopher(app *App, w gopher.ResponseWriter, r *gopher.Request) error {
parts := strings.Split(r.Selector, "/")
if app.cfg.App.SingleUser {
if parts[1] != "" {
return handleGopherCollectionPost(app, w, r)
}
return handleGopherCollection(app, w, r)
}
// Show all public collections (a gopher Reader view, essentially)
if len(parts) == 3 {
return handleGopherCollection(app, w, r)
}
w.WriteInfo(fmt.Sprintf("Welcome to %s", app.cfg.App.SiteName))
colls, err := app.db.GetPublicCollections(app.cfg.App.Host)
if err != nil {
return err
}
for _, c := range *colls {
w.WriteItem(&gopher.Item{
Host: stripHostProtocol(app),
Port: app.cfg.Server.GopherPort,
Type: gopher.DIRECTORY,
Description: c.DisplayTitle(),
Selector: "/" + c.Alias + "/",
})
}
return w.End()
}
func handleGopherCollection(app *App, w gopher.ResponseWriter, r *gopher.Request) error {
var collAlias, slug string
var c *Collection
var err error
var baseSel = "/"
parts := strings.Split(r.Selector, "/")
if app.cfg.App.SingleUser {
// sanity check
slug = parts[1]
if slug != "" {
return handleGopherCollectionPost(app, w, r)
}
c, err = app.db.GetCollectionByID(1)
if err != nil {
return err
}
} else {
collAlias = parts[1]
slug = parts[2]
if slug != "" {
return handleGopherCollectionPost(app, w, r)
}
c, err = app.db.GetCollection(collAlias)
if err != nil {
return err
}
baseSel = "/" + c.Alias + "/"
}
c.hostName = app.cfg.App.Host
w.WriteInfo(c.DisplayTitle())
if c.Description != "" {
w.WriteInfo(c.Description)
}
posts, err := app.db.GetPosts(app.cfg, c, 0, false, false, false)
if err != nil {
return err
}
for _, p := range *posts {
w.WriteItem(&gopher.Item{
Port: app.cfg.Server.GopherPort,
Host: stripHostProtocol(app),
Type: gopher.FILE,
Description: p.CreatedDate() + " - " + p.DisplayTitle(),
Selector: baseSel + p.Slug.String,
})
}
return w.End()
}
func handleGopherCollectionPost(app *App, w gopher.ResponseWriter, r *gopher.Request) error {
var collAlias, slug string
var c *Collection
var err error
parts := strings.Split(r.Selector, "/")
if app.cfg.App.SingleUser {
slug = parts[1]
c, err = app.db.GetCollectionByID(1)
if err != nil {
return err
}
} else {
collAlias = parts[1]
slug = parts[2]
c, err = app.db.GetCollection(collAlias)
if err != nil {
return err
}
}
c.hostName = app.cfg.App.Host
p, err := app.db.GetPost(slug, c.ID)
if err != nil {
return err
}
b := bytes.Buffer{}
if p.Title.String != "" {
b.WriteString(p.Title.String + "\n")
}
b.WriteString(p.DisplayDate + "\n\n")
b.WriteString(p.Content)
io.Copy(w, &b)
return w.End()
}

122
handle.go
View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018-2019 A Bunch Tell LLC.
* Copyright © 2018-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -23,8 +23,9 @@ import (
"github.com/gorilla/sessions"
"github.com/writeas/impart"
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/config"
"github.com/writeas/writefreely/page"
"github.com/writefreely/go-gopher"
"github.com/writefreely/writefreely/config"
"github.com/writefreely/writefreely/page"
)
// UserLevel represents the required user level for accessing an endpoint
@ -64,6 +65,7 @@ func UserLevelReader(cfg *config.Config) UserLevel {
type (
handlerFunc func(app *App, w http.ResponseWriter, r *http.Request) error
gopherFunc func(app *App, w gopher.ResponseWriter, r *gopher.Request) error
userHandlerFunc func(app *App, u *User, w http.ResponseWriter, r *http.Request) error
userApperHandlerFunc func(apper Apper, u *User, w http.ResponseWriter, r *http.Request) error
dataHandlerFunc func(app *App, w http.ResponseWriter, r *http.Request) ([]byte, string, error)
@ -153,8 +155,14 @@ func (h *Handler) User(f userHandlerFunc) http.HandlerFunc {
err := f(h.app.App(), u, w, r)
if err == nil {
status = http.StatusOK
} else if err, ok := err.(impart.HTTPError); ok {
status = err.Status
} else if impErr, ok := err.(impart.HTTPError); ok {
status = impErr.Status
if impErr == ErrUserNotFound {
log.Info("Logged-in user not found. Logging out.")
sendRedirect(w, http.StatusFound, "/me/logout?to="+h.app.App().cfg.App.LandingPath())
// Reset err so handleHTTPError does nothing
err = nil
}
} else {
status = http.StatusInternalServerError
}
@ -254,7 +262,7 @@ func apiAuth(app *App, r *http.Request) (*User, error) {
return u, nil
}
// optionaAPIAuth is used for endpoints that accept authenticated requests via
// optionalAPIAuth is used for endpoints that accept authenticated requests via
// Authorization header or cookie, unlike apiAuth. It returns a different err
// in the case where no Authorization header is present.
func optionalAPIAuth(app *App, r *http.Request) (*User, error) {
@ -285,6 +293,26 @@ func (h *Handler) UserAPI(f userHandlerFunc) http.HandlerFunc {
return h.UserAll(false, f, apiAuth)
}
// UserWebAPI handles endpoints that accept a user authorized either via the web (cookies) or an Authorization header.
func (h *Handler) UserWebAPI(f userHandlerFunc) http.HandlerFunc {
return h.UserAll(false, f, func(app *App, r *http.Request) (*User, error) {
// Authorize user via cookies
u := getUserSession(app, r)
if u != nil {
return u, nil
}
// Fall back to access token, since user isn't logged in via web
var err error
u, err = apiAuth(app, r)
if err != nil {
return nil, err
}
return u, nil
})
}
func (h *Handler) UserAll(web bool, f userHandlerFunc, a authFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
handleFunc := func() error {
@ -552,6 +580,38 @@ func (h *Handler) All(f handlerFunc) http.HandlerFunc {
}
}
func (h *Handler) PlainTextAPI(f handlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
h.handleTextError(w, r, func() error {
// TODO: return correct "success" status
status := 200
start := time.Now()
defer func() {
if e := recover(); e != nil {
log.Error("%s:\n%s", e, debug.Stack())
status = http.StatusInternalServerError
w.WriteHeader(status)
fmt.Fprintf(w, "Something didn't work quite right. The robots have alerted the humans.")
}
log.Info(fmt.Sprintf("\"%s %s\" %d %s \"%s\" \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent(), r.Host))
}()
err := f(h.app.App(), w, r)
if err != nil {
if err, ok := err.(impart.HTTPError); ok {
status = err.Status
} else {
status = http.StatusInternalServerError
}
}
return err
}())
}
}
func (h *Handler) OAuth(f handlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
h.handleOAuthError(w, r, func() error {
@ -599,6 +659,9 @@ func (h *Handler) AllReader(f handlerFunc) http.HandlerFunc {
log.Info(h.app.ReqLog(r, status, time.Since(start)))
}()
// Allow any origin, as public endpoints are handled in here
w.Header().Set("Access-Control-Allow-Origin", "*")
if h.app.App().cfg.App.Private {
// This instance is private, so ensure it's being accessed by a valid user
// Check if authenticated with an access token
@ -755,7 +818,7 @@ func (h *Handler) handleHTTPError(w http.ResponseWriter, r *http.Request, err er
return
} else if err.Status == http.StatusNotFound {
w.WriteHeader(err.Status)
if strings.Contains(r.Header.Get("Accept"), "application/activity+json") {
if IsActivityPubRequest(r) {
// This is a fediverse request; simply return the header
return
}
@ -817,6 +880,26 @@ func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error)
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
}
func (h *Handler) handleTextError(w http.ResponseWriter, r *http.Request, err error) {
if err == nil {
return
}
if err, ok := err.(impart.HTTPError); ok {
if err.Status >= 300 && err.Status < 400 {
sendRedirect(w, err.Status, err.Message)
return
}
w.WriteHeader(err.Status)
fmt.Fprintf(w, http.StatusText(err.Status))
return
}
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "This is an unhelpful error message for a miscellaneous internal error.")
}
func (h *Handler) handleOAuthError(w http.ResponseWriter, r *http.Request, err error) {
if err == nil {
return
@ -898,8 +981,33 @@ func (h *Handler) LogHandlerFunc(f http.HandlerFunc) http.HandlerFunc {
}
}
func (h *Handler) Gopher(f gopherFunc) gopher.HandlerFunc {
return func(w gopher.ResponseWriter, r *gopher.Request) {
defer func() {
if e := recover(); e != nil {
log.Error("%s: %s", e, debug.Stack())
w.WriteError("An internal error occurred")
}
log.Info("gopher: %s", r.Selector)
}()
err := f(h.app.App(), w, r)
if err != nil {
log.Error("failed: %s", err)
w.WriteError("the page failed for some reason (see logs)")
}
}
}
func sendRedirect(w http.ResponseWriter, code int, location string) int {
w.Header().Set("Location", location)
w.WriteHeader(code)
return code
}
func cacheControl(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "public, max-age=604800, immutable")
next.ServeHTTP(w, r)
})
}

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018-2019 A Bunch Tell LLC.
* Copyright © 2018-2019 Musing Studio LLC.
*
* This file is part of WriteFreely.
*

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018 A Bunch Tell LLC.
* Copyright © 2018 Musing Studio LLC.
*
* This file is part of WriteFreely.
*

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2019 A Bunch Tell LLC.
* Copyright © 2019-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -19,9 +19,9 @@ import (
"github.com/gorilla/mux"
"github.com/writeas/impart"
"github.com/writeas/nerds/store"
"github.com/writeas/web-core/id"
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/page"
"github.com/writefreely/writefreely/page"
)
type Invite struct {
@ -42,6 +42,18 @@ func (i Invite) Expired() bool {
return i.Expires != nil && i.Expires.Before(time.Now())
}
func (i Invite) Active(db *datastore) bool {
if i.Expired() {
return false
}
if i.MaxUses.Valid && i.MaxUses.Int64 > 0 {
if c := db.GetUsersInvitedCount(i.ID); c >= i.MaxUses.Int64 {
return false
}
}
return true
}
func (i Invite) ExpiresFriendly() string {
return i.Expires.Format("January 2, 2006, 3:04 PM")
}
@ -66,6 +78,9 @@ func handleViewUserInvites(app *App, u *User, w http.ResponseWriter, r *http.Req
p.Silenced, err = app.db.IsUserSilenced(u.ID)
if err != nil {
if err == ErrUserNotFound {
return err
}
log.Error("view invites: %v", err)
}
@ -109,7 +124,7 @@ func handleCreateUserInvite(app *App, u *User, w http.ResponseWriter, r *http.Re
expDate = &ed
}
inviteID := store.GenerateRandomString("0123456789BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz", 6)
inviteID := id.GenerateRandomString("0123456789BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz", 6)
err = app.db.CreateUserInvite(inviteID, u.ID, maxUses, expDate)
if err != nil {
return err
@ -158,12 +173,14 @@ func handleViewInvite(app *App, w http.ResponseWriter, r *http.Request) error {
p := struct {
page.StaticPage
*OAuthButtons
Error string
Flashes []template.HTML
Invite string
}{
StaticPage: pageForReq(app, r),
Invite: inviteCode,
StaticPage: pageForReq(app, r),
OAuthButtons: NewOAuthButtons(app.cfg),
Invite: inviteCode,
}
if expired {

72
jobs.go Normal file
View File

@ -0,0 +1,72 @@
package writefreely
import (
"github.com/writeas/web-core/log"
"time"
)
type PostJob struct {
ID int64
PostID string
Action string
Delay int64
}
func addJob(app *App, p *PublicPost, action string, delay int64) error {
j := &PostJob{
PostID: p.ID,
Action: action,
Delay: delay,
}
return app.db.InsertJob(j)
}
func startPublishJobsQueue(app *App) {
t := time.NewTicker(62 * time.Second)
for {
log.Info("[jobs] Done.")
<-t.C
log.Info("[jobs] Fetching email publish jobs...")
jobs, err := app.db.GetJobsToRun("email")
if err != nil {
log.Error("[jobs] %s - Skipping.", err)
continue
}
log.Info("[jobs] Running %d email publish jobs...", len(jobs))
err = runJobs(app, jobs, true)
if err != nil {
log.Error("[jobs] Failed: %s", err)
}
}
}
func runJobs(app *App, jobs []*PostJob, reqColl bool) error {
for _, j := range jobs {
p, err := app.db.GetPost(j.PostID, 0)
if err != nil {
log.Info("[job #%d] Unable to get post: %s", j.ID, err)
continue
}
if !p.CollectionID.Valid && reqColl {
log.Info("[job #%d] Post %s not part of a collection", j.ID, p.ID)
app.db.DeleteJob(j.ID)
continue
}
coll, err := app.db.GetCollectionByID(p.CollectionID.Int64)
if err != nil {
log.Info("[job #%d] Unable to get collection: %s", j.ID, err)
continue
}
coll.hostName = app.cfg.App.Host
coll.ForPublic()
p.Collection = &CollectionObj{Collection: *coll}
err = emailPost(app, p, p.Collection.ID)
if err != nil {
log.Error("[job #%d] Failed to email post %s", j.ID, p.ID)
continue
}
log.Info("[job #%d] Success for post %s.", j.ID, p.ID)
app.db.DeleteJob(j.ID)
}
return nil
}

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2019 A Bunch Tell LLC.
* Copyright © 2019, 2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -20,7 +20,7 @@ const (
)
type Keychain struct {
EmailKey, CookieAuthKey, CookieKey []byte
EmailKey, CookieAuthKey, CookieKey, CSRFKey []byte
}
// GenerateKeys generates necessary keys for the app on the given Keychain,
@ -47,6 +47,12 @@ func (keys *Keychain) GenerateKeys() error {
keyErrs = err
}
}
if len(keys.CSRFKey) == 0 {
keys.CSRFKey, err = GenerateBytes(EncKeysBytes)
if err != nil {
keyErrs = err
}
}
return keyErrs
}

11
keys.go
View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018-2019 A Bunch Tell LLC.
* Copyright © 2018-2019, 2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -12,8 +12,7 @@ package writefreely
import (
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/key"
"io/ioutil"
"github.com/writefreely/writefreely/key"
"os"
"path/filepath"
)
@ -26,6 +25,7 @@ var (
emailKeyPath = filepath.Join(keysDir, "email.aes256")
cookieAuthKeyPath = filepath.Join(keysDir, "cookies_auth.aes256")
cookieKeyPath = filepath.Join(keysDir, "cookies_enc.aes256")
csrfKeyPath = filepath.Join(keysDir, "csrf.aes256")
)
// InitKeys loads encryption keys into memory via the given Apper interface
@ -42,6 +42,7 @@ func initKeyPaths(app *App) {
emailKeyPath = filepath.Join(app.cfg.Server.KeysParentDir, emailKeyPath)
cookieAuthKeyPath = filepath.Join(app.cfg.Server.KeysParentDir, cookieAuthKeyPath)
cookieKeyPath = filepath.Join(app.cfg.Server.KeysParentDir, cookieKeyPath)
csrfKeyPath = filepath.Join(app.cfg.Server.KeysParentDir, csrfKeyPath)
}
// generateKey generates a key at the given path used for the encryption of
@ -50,7 +51,7 @@ func initKeyPaths(app *App) {
func generateKey(path string) error {
// Check if key file exists
if _, err := os.Stat(path); err == nil {
log.Info("%s already exists. rm the file if you understand the consquences.", path)
log.Info("%s already exists. rm the file if you understand the consequences.", path)
return nil
} else if !os.IsNotExist(err) {
log.Error("%s", err)
@ -63,7 +64,7 @@ func generateKey(path string) error {
log.Error("FAILED. %s. Run writefreely --gen-keys again.", err)
return err
}
err = ioutil.WriteFile(path, b, 0600)
err = os.WriteFile(path, b, 0600)
if err != nil {
log.Error("FAILED writing file: %s", err)
return err

View File

@ -5,6 +5,7 @@ all :
lessc app.less --clean-css="--s1 --advanced" $(CSSDIR)write.css
lessc fonts.less --clean-css="--s1 --advanced" $(CSSDIR)fonts.css
lessc icons.less --clean-css="--s1 --advanced" $(CSSDIR)icons.css
lessc prose.less --clean-css="--s1 --advanced" $(CSSDIR)prose.css
install :
./install-less.sh

View File

@ -32,6 +32,19 @@ nav#admin {
display: flex;
justify-content: center;
&:not(.pages) {
display: block;
margin: 0.5em 0;
a {
margin-left: 0;
.rounded(.25em);
&+a {
margin-left: 0.5em;
}
}
}
a {
color: #333;
font-family: @sansFont;
@ -47,6 +60,35 @@ nav#admin {
background: #ccc;
}
}
&.sub {
margin: 1em 0 2em;
a:not(.toggle) {
border: 0;
border-bottom: 2px transparent solid;
.rounded(0);
padding: 0.5em;
margin-left: 0.5em;
margin-right: 0.5em;
&:hover {
color: @primary;
background: transparent;
}
&.selected {
color: @primary;
background: transparent;
border-bottom-color: @primary;
}
&+a {
margin-left: 1em;
}
}
a.toggle {
margin-top: -0.5em;
float: right;
}
}
}
.admin-actions {

View File

@ -5,6 +5,8 @@
@import "post-temp";
@import "effects";
@import "admin";
@import "login";
@import "pages/error";
@import "resources";
@import "lib/elements";
@import "lib/material";

View File

@ -1,15 +1,3 @@
@primary: rgb(114, 120, 191);
@secondary: rgb(114, 191, 133);
@subheaders: #444;
@headerTextColor: black;
@sansFont: 'Open Sans', 'Segoe UI', Tahoma, Arial, sans-serif;
@serifFont: Lora, 'Palatino Linotype', 'Book Antiqua', 'New York', 'DejaVu serif', serif;
@monoFont: Hack, consolas, Menlo-Regular, Menlo, Monaco, 'ubuntu mono', monospace, monospace;
@dangerCol: #e21d27;
@errUrgentCol: #ecc63c;
@proSelectedCol: #71D571;
@textLinkColor: rgb(0, 0, 238);
body {
font-family: @serifFont;
font-size-adjust: 0.5;
@ -81,7 +69,7 @@ body {
font-size: 1.5em;
}
h2 {
font-size: 1.17em;
font-size: 1.4em;
}
}
@ -222,6 +210,10 @@ body {
pre {
line-height: 1.5;
}
.flash {
text-align: center;
margin-bottom: 4em;
}
}
&#subpage {
#wrapper {
@ -405,6 +397,14 @@ body {
}
}
img {
&.paid {
height: 0.86em;
vertical-align: middle;
margin-bottom: 0.1em;
}
}
nav#full-nav {
margin: 0;
@ -527,7 +527,7 @@ pre, body#post article, #post .alert, #subpage .alert, body#collection article,
line-height: 1.5;
}
}
textarea, pre, body#post article, body#collection article p {
textarea, input#title, pre, body#post article, body#collection article p {
&.norm, &.sans, &.wrap {
line-height: 1.5;
white-space: pre-wrap; /* CSS 3 */
@ -537,7 +537,7 @@ textarea, pre, body#post article, body#collection article p {
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
}
textarea, pre, body#post article, body#collection article, body#subpage article, span, .font {
textarea, input#title, pre, body#post article, body#collection article, body#subpage article, span, .font {
&.norm {
font-family: @serifFont;
}
@ -699,6 +699,7 @@ table.downloads {
select.inputform, textarea.inputform {
border: 1px solid #999;
background: white;
}
input, button, select.inputform, textarea.inputform, a.btn {
@ -743,6 +744,31 @@ input, button, select.inputform, textarea.inputform, a.btn {
}
}
.btn.pager {
border: 1px solid @lightNavBorder;
font-size: .86em;
padding: .5em 1em;
white-space: nowrap;
font-family: @sansFont;
&:hover {
text-decoration: none;
background: @lightNavBorder;
}
}
.btn.cta.secondary, input[type=submit].secondary {
background: transparent;
color: @primary;
&:hover {
background-color: #f9f9f9;
}
}
.btn.cta.disabled {
background-color: desaturate(@primary, 100%) !important;
border-color: desaturate(@primary, 100%) !important;
}
div.flat-select {
display: inline-block;
position: relative;
@ -805,6 +831,9 @@ input {
margin: 0 auto 3em;
font-size: 1.2em;
&.toosmall {
max-width: 25em;
}
&.tight {
max-width: 30em;
}
@ -965,7 +994,12 @@ footer.contain-me {
}
ul {
&.collections {
padding-left: 0;
margin-left: 0;
h3 {
margin-top: 0;
font-weight: normal;
}
li {
&.collection {
a.title {
@ -1039,6 +1073,19 @@ li {
background-color: #dff0d8;
border-color: #d6e9c6;
}
&.danger {
border-color: #856404;
background-color: white;
h3 {
margin: 0 0 0.5em 0;
font-size: 1em;
font-weight: bold;
color: black !important;
}
h3 + p, button {
font-size: 0.86em;
}
}
p {
margin: 0;
@ -1095,7 +1142,8 @@ body#pad-sub #posts, .atoms {
}
.electron {
font-weight: normal;
margin-left: 0.5em;
font-size: 0.86em;
margin-left: 0.75rem;
}
}
h3, h4 {
@ -1245,7 +1293,7 @@ header {
}
}
&.singleuser {
margin: 0.5em 0.25em;
margin: 0.5em 1em 0.5em 0.25em;
nav#user-nav {
nav > ul > li:first-child {
img {
@ -1253,6 +1301,9 @@ header {
}
}
}
.right-side {
padding-top: 0.5em;
}
}
.dash-nav {
font-weight: bold;
@ -1506,6 +1557,11 @@ div.row {
margin-left: 0;
margin-top: 0;
}
article {
.hidden {
.opacity(1);
}
}
}
@media print {
@ -1547,3 +1603,38 @@ div.row {
pre.code-block {
overflow-x: auto;
}
#emailsub {
text-align: center;
}
p#emailsub {
display: inline-block !important;
width: 100%;
font-style: italic;
}
#subscribe-btn {
margin-left: 0.5em;
}
#org-nav {
font-family: @sansFont;
font-size: 1.1em;
color: #888;
em, strong {
color: #000;
}
&+h1 {
margin-top: 0.5em;
}
a:link, a:visited, a:hover {
color: @accent;
}
a:first-child {
margin-right: 0.25em;
}
a.coll-name {
font-weight: bold;
margin-left: 0.25em;
}
}

View File

@ -3,7 +3,6 @@
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: optional;
src: url('/fonts/open-sans-v13-latin-regular.eot'); /* IE9 Compat Modes */
src: local('Open Sans'), local('OpenSans'),
url('/fonts/open-sans-v13-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
@ -17,7 +16,6 @@
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-display: optional;
src: url('/fonts/open-sans-v13-latin-700.eot'); /* IE9 Compat Modes */
src: local('Open Sans Bold'), local('OpenSans-Bold'),
url('/fonts/open-sans-v13-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
@ -31,7 +29,6 @@
font-family: 'Lora';
font-style: normal;
font-weight: 400;
font-display: optional;
src: url('/fonts/Lora-Regular.eot'); /* IE9 Compat Modes */
src: local('Lora'), local('Lora-Regular'),
url('/fonts/Lora-Regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
@ -44,7 +41,6 @@
font-family: 'Lora';
font-style: normal;
font-weight: 700;
font-display: optional;
src: url('/fonts/Lora-Bold.eot'); /* IE9 Compat Modes */
src: local('Lora Bold'), local('Lora-Bold'),
url('/fonts/Lora-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
@ -56,7 +52,6 @@
font-family: 'Lora';
font-style: italic;
font-weight: 400;
font-display: optional;
src: url('/fonts/Lora-Italic.eot'); /* IE9 Compat Modes */
src: local('Lora Italic'), local('Lora-Italic'),
url('/fonts/Lora-Italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */

View File

@ -2,7 +2,7 @@
# Install Less via npm
if [ ! -e "$(which lessc)" ]; then
sudo npm install -g less
sudo npm install -g less@3.5.3
sudo npm install -g less-plugin-clean-css
else
echo LESS $(npm view less version 2>&1 | grep -v WARN) is installed

91
less/login.less Normal file
View File

@ -0,0 +1,91 @@
/*
* Copyright © 2020 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
.row.signinbtns {
justify-content: center;
font-size: 1em;
margin-top: 2em;
margin-bottom: 1em;
flex-wrap: wrap;
.loginbtn {
height: 40px;
margin: 0.5em;
&.btn {
box-sizing: border-box;
font-size: 17px;
white-space: nowrap;
img {
height: 1.5em;
vertical-align: middle;
}
}
&#writeas-login, &#slack-login {
img {
margin-top: -0.2em;
}
}
&#gitlab-login {
background-color: #fc6d26;
border-color: #fc6d26;
&:hover {
background-color: darken(#fc6d26, 5%);
border-color: darken(#fc6d26, 5%);
}
}
&#gitea-login {
background-color: #2ecc71;
border-color: #2ecc71;
&:hover {
background-color: #2cc26b;
border-color: #2cc26b;
}
}
&#slack-login, &#gitlab-login, &#gitea-login, &#generic-oauth-login {
font-size: 0.86em;
font-family: @sansFont;
}
&#slack-login, &#generic-oauth-login {
color: @lightTextColor;
background-color: @lightNavBG;
border-color: @lightNavBorder;
&:hover {
background-color: @lightNavHoverBG;
}
}
}
}
.or {
text-align: center;
margin-bottom: 3.5em;
p {
display: inline-block;
background-color: white;
padding: 0 1em;
}
hr {
margin-top: -1.6em;
margin-bottom: 0;
}
hr.short {
max-width: 30rem;
}
}

View File

@ -127,7 +127,6 @@ textarea {
&.collection {
a.title {
font-size: 1.3em;
font-weight: bold;
}
}
}

View File

@ -188,18 +188,18 @@ body#pad, body#pad-sub {
body#pad {
.pad-theme-transition;
textarea {
textarea, #title {
.pad-theme-transition;
}
&.dark {
textarea {
textarea, #title, #editor {
background-color: @darkBG;
color: @darkTextColor;
}
}
&.light {
textarea {
textarea, #title, #editor {
background-color: @lightBG;
color: @lightTextColor;
}

View File

@ -60,7 +60,7 @@
&:hover {
background: @lightNavHoverBG;
}
&:hover > ul {
&:hover > ul, &.open > ul {
display: block;
}
&.selected {
@ -256,7 +256,7 @@ body#pad {
border: 0;
outline: 0;
}
textarea {
textarea, #title {
position: fixed !important;
top: 3em;
right: 0;
@ -340,6 +340,15 @@ body#pad {
}
}
.body {
line-height: 1.5;
input[type=text].confirm {
width: 100%;
box-sizing: border-box;
}
}
.short {
text-align: center;
}
@ -361,12 +370,38 @@ body#pad {
z-index: 10;
}
body#pad .alert {
position: fixed;
bottom: 0.25em;
left: 2em;
right: 2em;
font-size: 1.1em;
&#edited-elsewhere {
&.hidden {
display: none;
}
a {
font-weight: bold;
}
}
}
@media all and (max-height: 500px) {
body#pad {
textarea {
top: 2.25em;
padding-top: 0.25em;
}
&.classic {
#editor {
top: 5.25em;
}
#title {
top: 3.5rem;
}
}
#tools {
padding-top: 0.5em;
padding-bottom: 0.5em;
@ -420,43 +455,63 @@ body#pad {
}
@media all and (min-width: 50em) {
body#pad {
textarea {
body#pad, body#pad.classic {
textarea, #title {
padding-left: 10%;
padding-right: 10%;
}
.alert {
left: 10%;
right: 10%;
}
}
}
@media all and (min-width: 60em) {
body#pad {
textarea {
body#pad, body#pad.classic {
textarea, #title {
padding-left: 15%;
padding-right: 15%;
}
.alert {
left: 15%;
right: 15%;
}
}
}
@media all and (min-width: 70em) {
body#pad {
textarea {
body#pad, body#pad.classic {
textarea, #title {
padding-left: 20%;
padding-right: 20%;
}
.alert {
left: 20%;
right: 20%;
}
}
}
@media all and (min-width: 85em) {
body#pad {
textarea {
body#pad, body#pad.classic {
textarea, #title {
padding-left: 25%;
padding-right: 25%;
}
.alert {
left: 25%;
right: 25%;
}
}
}
@media all and (min-width: 105em) {
body#pad {
textarea {
body#pad, body#pad.classic {
textarea, #title {
padding-left: 30%;
padding-right: 30%;
}
.alert {
left: 30%;
right: 30%;
}
}
}
@media (pointer: coarse) {

View File

@ -37,6 +37,25 @@ body#post article, pre, .hljs {
font-size: 1.2em;
}
p.split {
color: #6161FF;
font-style: italic;
font-size: 0.86em;
}
#readmore-sell {
padding: 1em 1em 2em;
background-color: #fafafa;
p.split {
color: black;
font-style: normal;
font-size: 1.4em;
}
.cta + .cta {
margin-left: 0.5em;
}
}
/* Post mixins */
.article-code() {
background-color: #f8f8f8;

490
less/prose-editor.less Normal file
View File

@ -0,0 +1,490 @@
@classicHorizMargin: 2rem;
body#pad.classic {
header {
display: flex;
justify-content: space-between;
align-items: center;
}
#editor {
top: 4em;
bottom: 1em;
}
#title {
top: 4.25rem;
bottom: unset;
height: auto;
font-weight: bold;
font-size: 2em;
padding: 0;
border: 0;
}
#tools {
#belt {
float: none;
}
}
#target {
ul {
a {
padding: 0 0.5em !important;
}
}
}
}
#title {
margin-left: @classicHorizMargin;
margin-right: @classicHorizMargin;
}
.ProseMirror {
position: relative;
height: calc(~"100% - 1.6em");
overflow-y: auto;
box-sizing: border-box;
-moz-box-sizing: border-box;
font-size: 1.2em;
word-wrap: break-word;
white-space: pre-wrap;
-webkit-font-variant-ligatures: none;
font-variant-ligatures: none;
padding: 0.5em @classicHorizMargin;
line-height: 1.5;
outline: none;
}
.ProseMirror pre {
white-space: pre-wrap;
}
.ProseMirror li {
position: relative;
}
.ProseMirror-hideselection *::selection {
background: transparent;
}
.ProseMirror-hideselection *::-moz-selection {
background: transparent;
}
.ProseMirror-hideselection {
caret-color: transparent;
}
.ProseMirror-selectednode {
outline: 2px solid #8cf;
}
/* Make sure li selections wrap around markers */
li.ProseMirror-selectednode {
outline: none;
}
li.ProseMirror-selectednode:after {
content: "";
position: absolute;
left: -32px;
right: -2px;
top: -2px;
bottom: -2px;
border: 2px solid #8cf;
pointer-events: none;
}
.ProseMirror-textblock-dropdown {
min-width: 3em;
}
.ProseMirror-menu {
margin: 0 -4px;
line-height: 1;
}
.ProseMirror-tooltip .ProseMirror-menu {
width: -webkit-fit-content;
width: fit-content;
white-space: pre;
}
.ProseMirror-menuitem {
margin-right: 3px;
display: inline-block;
div {
cursor: pointer;
}
}
.ProseMirror-menuseparator {
border-right: 1px solid #ddd;
margin-right: 3px;
}
.ProseMirror-menu-dropdown, .ProseMirror-menu-dropdown-menu {
font-size: 90%;
white-space: nowrap;
}
.ProseMirror-menu-dropdown {
vertical-align: 1px;
cursor: pointer;
position: relative;
padding-right: 15px;
}
.ProseMirror-menu-dropdown-wrap {
padding: 1px 0 1px 4px;
display: inline-block;
position: relative;
}
.ProseMirror-menu-dropdown:after {
content: "";
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid currentColor;
opacity: .6;
position: absolute;
right: 4px;
top: calc(50% - 2px);
}
.ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu {
position: absolute;
background: white;
color: #666;
border: 1px solid #aaa;
padding: 2px;
}
.ProseMirror-menu-dropdown-menu {
z-index: 15;
min-width: 6em;
}
.ProseMirror-menu-dropdown-item {
cursor: pointer;
padding: 2px 8px 2px 4px;
}
.ProseMirror-menu-dropdown-item:hover {
background: #f2f2f2;
}
.ProseMirror-menu-submenu-wrap {
position: relative;
margin-right: -4px;
}
.ProseMirror-menu-submenu-label:after {
content: "";
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
border-left: 4px solid currentColor;
opacity: .6;
position: absolute;
right: 4px;
top: calc(50% - 4px);
}
.ProseMirror-menu-submenu {
display: none;
min-width: 4em;
left: 100%;
top: -3px;
}
.ProseMirror-menu-active {
background: #eee;
border-radius: 4px;
}
.ProseMirror-menu-active {
background: #eee;
border-radius: 4px;
}
.ProseMirror-menu-disabled {
opacity: .3;
}
.ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu, .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu {
display: block;
}
.ProseMirror-menubar {
font-family: @sansFont;
position: relative;
min-height: 1em;
color: #666;
padding: 0.5em;
top: 0;
left: 0;
right: 0;
background: rgba(255, 255, 255, 0.8);
z-index: 10;
-moz-box-sizing: border-box;
box-sizing: border-box;
overflow: visible;
margin-left: @classicHorizMargin;
margin-right: @classicHorizMargin;
}
.ProseMirror-icon {
display: inline-block;
line-height: .8;
vertical-align: -2px; /* Compensate for padding */
padding: 2px 8px;
cursor: pointer;
}
.ProseMirror-menu-disabled.ProseMirror-icon {
cursor: default;
}
.ProseMirror-icon svg {
fill: currentColor;
height: 1em;
}
.ProseMirror-icon span {
vertical-align: text-top;
}
.ProseMirror-gapcursor {
display: none;
pointer-events: none;
position: absolute;
}
.ProseMirror-gapcursor:after {
content: "";
display: block;
position: absolute;
top: -2px;
width: 20px;
border-top: 1px solid black;
animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
}
@keyframes ProseMirror-cursor-blink {
to {
visibility: hidden;
}
}
.ProseMirror-focused .ProseMirror-gapcursor {
display: block;
}
/* Add space around the hr to make clicking it easier */
.ProseMirror-example-setup-style hr {
padding: 4px 10px;
border: none;
margin: 1em 0;
background: initial;
}
.ProseMirror-example-setup-style hr:after {
content: "";
display: block;
height: 1px;
background-color: #ccc;
line-height: 2px;
}
.ProseMirror ul, .ProseMirror ol {
padding-left: 30px;
}
.ProseMirror blockquote {
padding-left: 1em;
border-left: 4px solid #ddd;
color: #767676;
margin-left: 0;
margin-right: 0;
}
.ProseMirror-example-setup-style img {
cursor: default;
max-width: 100%;
}
.ProseMirror-prompt {
background: white;
padding: 1em;
border: 1px solid silver;
position: fixed;
border-radius: 0.25em;
z-index: 11;
box-shadow: -.5px 2px 5px rgba(0, 0, 0, .2);
}
.ProseMirror-prompt h5 {
margin: 0 0 0.75em;
font-family: @sansFont;
font-size: 100%;
color: #444;
}
.ProseMirror-prompt input[type="text"],
.ProseMirror-prompt textarea {
background: #eee;
border: none;
outline: none;
}
.ProseMirror-prompt input[type="text"] {
margin: 0.25em 0;
}
.ProseMirror-prompt-close {
position: absolute;
left: 2px;
top: 1px;
color: #666;
border: none;
background: transparent;
padding: 0;
}
.ProseMirror-prompt-close:after {
content: "✕";
font-size: 12px;
}
.ProseMirror-invalid {
background: #ffc;
border: 1px solid #cc7;
border-radius: 4px;
padding: 5px 10px;
position: absolute;
min-width: 10em;
}
.ProseMirror-prompt-buttons {
margin-top: 5px;
display: none;
}
#editor, .editor {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
color: black;
background-clip: padding-box;
padding: 5px 0;
margin: 4em auto 23px auto;
}
.dark #editor {
color: white;
}
.ProseMirror p:first-child,
.ProseMirror h1:first-child,
.ProseMirror h2:first-child,
.ProseMirror h3:first-child,
.ProseMirror h4:first-child,
.ProseMirror h5:first-child,
.ProseMirror h6:first-child {
margin-top: 10px;
}
.ProseMirror p {
margin-bottom: 1em;
}
textarea {
width: 100%;
height: 123px;
border: 1px solid silver;
box-sizing: border-box;
-moz-box-sizing: border-box;
padding: 3px 10px;
border: none;
outline: none;
font-family: inherit;
font-size: inherit;
}
.ProseMirror-menubar-wrapper {
height: 100%;
box-sizing: border-box;
}
.ProseMirror-menubar-wrapper, #markdown textarea {
display: block;
margin-bottom: 4px;
}
.editorreadmore {
color: @textLinkColor;
text-decoration: underline;
text-align: center;
width: 100%;
}
@media all and (min-width: 50em) {
#photo-upload label {
display: inline;
}
.ProseMirror-menubar, #title, #photo-upload {
margin-left: 10%;
margin-right: 10%;
}
.ProseMirror {
padding-left: 10%;
padding-right: 10%;
}
}
@media all and (min-width: 60em) {
.ProseMirror-menubar, #title, #photo-upload {
margin-left: 15%;
margin-right: 15%;
}
.ProseMirror {
padding-left: 15%;
padding-right: 15%;
}
}
@media all and (min-width: 70em) {
.ProseMirror-menubar, #title, #photo-upload {
margin-left: 20%;
margin-right: 20%;
}
.ProseMirror {
padding-left: 20%;
padding-right: 20%;
}
}
@media all and (min-width: 85em) {
.ProseMirror-menubar, #title, #photo-upload {
margin-left: 25%;
margin-right: 25%;
}
.ProseMirror {
padding-left: 25%;
padding-right: 25%;
}
}
@media all and (min-width: 105em) {
.ProseMirror-menubar, #title, #photo-upload {
margin-left: 30%;
margin-right: 30%;
}
.ProseMirror {
padding-left: 30%;
padding-right: 30%;
}
}

4
less/prose.less Normal file
View File

@ -0,0 +1,4 @@
@import "prose-editor";
@import "pad-theme";
@import "resources";
@import "lib/elements";

13
less/resources.less Normal file
View File

@ -0,0 +1,13 @@
@primary: rgb(114, 120, 191);
@secondary: rgb(114, 191, 133);
@subheaders: #444;
@headerTextColor: black;
@sansFont: 'Open Sans', 'Segoe UI', Tahoma, Arial, sans-serif;
@serifFont: Lora, 'Palatino Linotype', 'Book Antiqua', 'New York', 'DejaVu serif', serif;
@monoFont: Hack, consolas, Menlo-Regular, Menlo, Monaco, 'ubuntu mono', monospace, monospace;
@dangerCol: #e21d27;
@errUrgentCol: #ecc63c;
@proSelectedCol: #71D571;
@textLinkColor: rgb(0, 0, 238);
@accent: #767676;

View File

@ -150,4 +150,4 @@ func countRows(t *testing.T, ctx context.Context, db *sql.DB, count int, query s
err := db.QueryRowContext(ctx, query, args...).Scan(&returned)
assert.NoError(t, err, "error executing query %s and args %s", query, args)
assert.Equal(t, count, returned, "unexpected return count %d, expected %d from %s and args %s", returned, count, query, args)
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2019 A Bunch Tell LLC.
* Copyright © 2019 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -36,6 +36,13 @@ func (db *datastore) typeSmallInt() string {
return "SMALLINT"
}
func (db *datastore) typeTinyInt() string {
if db.driverName == driverSQLite {
return "INTEGER"
}
return "TINYINT"
}
func (db *datastore) typeText() string {
return "TEXT"
}
@ -54,6 +61,13 @@ func (db *datastore) typeVarChar(l int) string {
return fmt.Sprintf("VARCHAR(%d)", l)
}
func (db *datastore) typeVarBinary(l int) string {
if db.driverName == driverSQLite {
return "BLOB"
}
return fmt.Sprintf("VARBINARY(%d)", l)
}
func (db *datastore) typeBool() string {
if db.driverName == driverSQLite {
return "INTEGER"
@ -65,6 +79,15 @@ func (db *datastore) typeDateTime() string {
return "DATETIME"
}
func (db *datastore) typeIntPrimaryKey() string {
if db.driverName == driverSQLite {
// From docs: "In SQLite, a column with type INTEGER PRIMARY KEY is an alias for the ROWID (except in WITHOUT
// ROWID tables) which is always a 64-bit signed integer."
return "INTEGER PRIMARY KEY"
}
return "INT AUTO_INCREMENT PRIMARY KEY"
}
func (db *datastore) collateMultiByte() string {
if db.driverName == driverSQLite {
return ""
@ -78,3 +101,10 @@ func (db *datastore) engine() string {
}
return " ENGINE = InnoDB"
}
func (db *datastore) after(colName string) string {
if db.driverName == driverSQLite {
return ""
}
return " AFTER " + colName
}

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2019 A Bunch Tell LLC.
* Copyright © 2019 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -61,8 +61,16 @@ var migrations = []Migration{
New("support users suspension", supportUserStatus), // V2 -> V3 (v0.11.0)
New("support oauth", oauth), // V3 -> V4
New("support slack oauth", oauthSlack), // V4 -> v5
New("support ActivityPub mentions", supportActivityPubMentions), // V5 -> V6 (v0.12.0)
New("support ActivityPub mentions", supportActivityPubMentions), // V5 -> V6
New("support oauth attach", oauthAttach), // V6 -> V7
New("support oauth via invite", oauthInvites), // V7 -> V8 (v0.12.0)
New("optimize drafts retrieval", optimizeDrafts), // V8 -> V9
New("support post signatures", supportPostSignatures), // V9 -> V10 (v0.13.0)
New("Widen oauth_users.access_token", widenOauthAcceesToken), // V10 -> V11
New("support verifying fedi profile", fediverseVerifyProfile), // V11 -> V12 (v0.14.0)
New("support newsletters", supportLetters), // V12 -> V13
New("support password resetting", supportPassReset), // V13 -> V14
New("speed up blog post retrieval", addPostRetrievalIndex), // V14 -> V15
}
// CurrentVer returns the current migration version the application is on
@ -84,6 +92,9 @@ func Migrate(db *datastore) error {
var err error
if db.tableExists("appmigrations") {
err = db.QueryRow("SELECT MAX(version) FROM appmigrations").Scan(&version)
if err != nil {
return err
}
} else {
log.Info("Initializing appmigrations table...")
version = 0

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2019 A Bunch Tell LLC.
* Copyright © 2019 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -12,6 +12,9 @@ package migrations
func supportUserInvites(db *datastore) error {
t, err := db.Begin()
if err != nil {
return err
}
_, err = t.Exec(`CREATE TABLE userinvites (
id ` + db.typeChar(6) + ` NOT NULL ,
owner_id ` + db.typeInt() + ` NOT NULL ,

33
migrations/v10.go Normal file
View File

@ -0,0 +1,33 @@
/*
* Copyright © 2020 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package migrations
func supportPostSignatures(db *datastore) error {
t, err := db.Begin()
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec(`ALTER TABLE collections ADD COLUMN post_signature ` + db.typeText() + db.collateMultiByte() + ` NULL` + db.after("script"))
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
return nil
}

38
migrations/v11.go Normal file
View File

@ -0,0 +1,38 @@
/*
* Copyright © 2020 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package migrations
/**
* Widen `oauth_users.access_token`, necessary only for mysql
*/
func widenOauthAcceesToken(db *datastore) error {
if db.driverName == driverMySQL {
t, err := db.Begin()
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec(`ALTER TABLE oauth_users MODIFY COLUMN access_token ` + db.typeText() + db.collateMultiByte() + ` NULL`)
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
}
return nil
}

33
migrations/v12.go Normal file
View File

@ -0,0 +1,33 @@
/*
* Copyright © 2023 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package migrations
func fediverseVerifyProfile(db *datastore) error {
t, err := db.Begin()
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec(`ALTER TABLE remoteusers ADD COLUMN url ` + db.typeVarChar(255) + ` NULL` + db.after("shared_inbox"))
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
return nil
}

58
migrations/v13.go Normal file
View File

@ -0,0 +1,58 @@
/*
* Copyright © 2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package migrations
func supportLetters(db *datastore) error {
t, err := db.Begin()
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec(`CREATE TABLE publishjobs (
id ` + db.typeIntPrimaryKey() + `,
post_id ` + db.typeVarChar(16) + ` not null,
action ` + db.typeVarChar(16) + ` not null,
delay ` + db.typeTinyInt() + ` not null
)`)
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec(`CREATE TABLE emailsubscribers (
id ` + db.typeChar(8) + ` not null,
collection_id ` + db.typeInt() + ` not null,
user_id ` + db.typeInt() + ` null,
email ` + db.typeVarChar(255) + ` null,
subscribed ` + db.typeDateTime() + ` not null,
token ` + db.typeChar(16) + ` not null,
confirmed ` + db.typeBool() + ` default 0 not null,
allow_export ` + db.typeBool() + ` default 0 not null,
constraint eu_coll_email
unique (collection_id, email),
constraint eu_coll_user
unique (collection_id, user_id),
PRIMARY KEY (id)
)`)
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
return nil
}

37
migrations/v14.go Normal file
View File

@ -0,0 +1,37 @@
/*
* Copyright © 2023 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package migrations
func supportPassReset(db *datastore) error {
t, err := db.Begin()
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec(`CREATE TABLE password_resets (
user_id ` + db.typeInt() + ` not null,
token ` + db.typeChar(32) + ` not null primary key,
used ` + db.typeBool() + ` default 0 not null,
created ` + db.typeDateTime() + ` not null
)`)
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
return nil
}

33
migrations/v15.go Normal file
View File

@ -0,0 +1,33 @@
/*
* Copyright © 2023 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package migrations
func addPostRetrievalIndex(db *datastore) error {
t, err := db.Begin()
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec("CREATE INDEX posts_get_collection_index ON posts (`collection_id`, `pinned_position`, `created`)")
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
return nil
}

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2019 A Bunch Tell LLC.
* Copyright © 2019 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -12,6 +12,9 @@ package migrations
func supportInstancePages(db *datastore) error {
t, err := db.Begin()
if err != nil {
return err
}
_, err = t.Exec(`ALTER TABLE appcontent ADD COLUMN title ` + db.typeVarChar(255) + db.collateMultiByte() + ` NULL`)
if err != nil {

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2019 A Bunch Tell LLC.
* Copyright © 2019 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -12,6 +12,9 @@ package migrations
func supportUserStatus(db *datastore) error {
t, err := db.Begin()
if err != nil {
return err
}
_, err = t.Exec(`ALTER TABLE users ADD COLUMN status ` + db.typeInt() + ` DEFAULT '0' NOT NULL`)
if err != nil {

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2019-2020 A Bunch Tell LLC.
* Copyright © 2019-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -14,7 +14,7 @@ import (
"context"
"database/sql"
wf_db "github.com/writeas/writefreely/db"
wf_db "github.com/writefreely/writefreely/db"
)
func oauth(db *datastore) error {

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2019-2020 A Bunch Tell LLC.
* Copyright © 2019-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -14,7 +14,7 @@ import (
"context"
"database/sql"
wf_db "github.com/writeas/writefreely/db"
wf_db "github.com/writefreely/writefreely/db"
)
func oauthSlack(db *datastore) error {

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2019-2020 A Bunch Tell LLC.
* Copyright © 2019-2020 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -12,6 +12,9 @@ package migrations
func supportActivityPubMentions(db *datastore) error {
t, err := db.Begin()
if err != nil {
return err
}
_, err = t.Exec(`ALTER TABLE remoteusers ADD COLUMN handle ` + db.typeVarChar(255) + ` NULL`)
if err != nil {

View File

@ -1,10 +1,20 @@
/*
* Copyright © 2020-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package migrations
import (
"context"
"database/sql"
wf_db "github.com/writeas/writefreely/db"
wf_db "github.com/writefreely/writefreely/db"
)
func oauthAttach(db *datastore) error {

45
migrations/v8.go Normal file
View File

@ -0,0 +1,45 @@
/*
* Copyright © 2020-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package migrations
import (
"context"
"database/sql"
wf_db "github.com/writefreely/writefreely/db"
)
func oauthInvites(db *datastore) error {
dialect := wf_db.DialectMySQL
if db.driverName == driverSQLite {
dialect = wf_db.DialectSQLite
}
return wf_db.RunTransactionWithOptions(context.Background(), db.DB, &sql.TxOptions{}, func(ctx context.Context, tx *sql.Tx) error {
builders := []wf_db.SQLBuilder{
dialect.
AlterTable("oauth_client_states").
AddColumn(dialect.Column("invite_code", wf_db.ColumnTypeChar, wf_db.OptionalInt{
Set: true,
Value: 6,
}).SetNullable(true)),
}
for _, builder := range builders {
query, err := builder.ToSQL()
if err != nil {
return err
}
if _, err := tx.ExecContext(ctx, query); err != nil {
return err
}
}
return nil
})
}

37
migrations/v9.go Normal file
View File

@ -0,0 +1,37 @@
/*
* Copyright © 2020 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package migrations
func optimizeDrafts(db *datastore) error {
t, err := db.Begin()
if err != nil {
t.Rollback()
return err
}
if db.driverName == driverSQLite {
_, err = t.Exec(`CREATE INDEX key_owner_post_id ON posts (owner_id, id)`)
} else {
_, err = t.Exec(`ALTER TABLE posts ADD INDEX(owner_id, id)`)
}
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
return nil
}

160
monetization.go Normal file
View File

@ -0,0 +1,160 @@
/*
* Copyright © 2020-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package writefreely
import (
"bytes"
"fmt"
"github.com/gorilla/mux"
"github.com/writeas/impart"
"github.com/writeas/web-core/log"
"io"
"net/http"
"net/url"
"os"
"strings"
)
func displayMonetization(monetization, alias string) string {
if monetization == "" {
return ""
}
ptrURL, err := url.Parse(strings.Replace(monetization, "$", "https://", 1))
if err == nil {
if strings.HasSuffix(ptrURL.Host, ".xrptipbot.com") {
// xrp tip bot doesn't support stream receipts, so return plain pointer
return monetization
}
}
u := os.Getenv("PAYMENT_HOST")
if u == "" {
return "$webmonetization.org/api/receipts/" + url.PathEscape(monetization)
}
u += "/" + alias
return u
}
func handleSPSPEndpoint(app *App, w http.ResponseWriter, r *http.Request) error {
idStr := r.FormValue("id")
id, err := url.QueryUnescape(idStr)
if err != nil {
log.Error("Unable to unescape: %s", err)
return err
}
var c *Collection
if strings.IndexRune(id, '.') > 0 && app.cfg.App.SingleUser {
c, err = app.db.GetCollectionByID(1)
} else {
c, err = app.db.GetCollection(id)
}
if err != nil {
return err
}
pointer := c.Monetization
if pointer == "" {
err := impart.HTTPError{http.StatusNotFound, "No monetization pointer."}
return err
}
fmt.Fprintf(w, pointer)
return nil
}
func handleGetSplitContent(app *App, w http.ResponseWriter, r *http.Request) error {
var collID int64
var collLookupID string
var coll *Collection
var err error
vars := mux.Vars(r)
if collAlias := vars["alias"]; collAlias != "" {
// Fetch collection information, since an alias is provided
coll, err = app.db.GetCollection(collAlias)
if err != nil {
return err
}
collID = coll.ID
collLookupID = coll.Alias
}
p, err := app.db.GetPost(vars["post"], collID)
if err != nil {
return err
}
receipt := r.FormValue("receipt")
if receipt == "" {
return impart.HTTPError{http.StatusBadRequest, "No `receipt` given."}
}
err = verifyReceipt(receipt, collLookupID)
if err != nil {
return err
}
d := struct {
Content string `json:"body"`
HTMLContent string `json:"html_body"`
}{}
if exc := strings.Index(p.Content, shortCodePaid); exc > -1 {
baseURL := ""
if coll != nil {
baseURL = coll.CanonicalURL()
}
d.Content = p.Content[exc+len(shortCodePaid):]
d.HTMLContent = applyMarkdown([]byte(d.Content), baseURL, app.cfg)
}
return impart.WriteSuccess(w, d, http.StatusOK)
}
func verifyReceipt(receipt, id string) error {
receiptsHost := os.Getenv("RECEIPTS_HOST")
if receiptsHost == "" {
receiptsHost = "https://webmonetization.org/api/receipts/verify?id=" + id
} else {
receiptsHost = fmt.Sprintf("%s/receipts?id=%s", receiptsHost, id)
}
log.Info("Verifying receipt %s at %s", receipt, receiptsHost)
r, err := http.NewRequest("POST", receiptsHost, bytes.NewBufferString(receipt))
if err != nil {
log.Error("Unable to create new request to %s: %s", receiptsHost, err)
return err
}
resp, err := http.DefaultClient.Do(r)
if err != nil {
log.Error("Unable to Do() request to %s: %s", receiptsHost, err)
return err
}
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Error("Unable to read %s response body: %s", receiptsHost, err)
return err
}
log.Info("Status : %s", resp.Status)
log.Info("Response: %s", body)
if resp.StatusCode != http.StatusOK {
log.Error("Bad response from %s:\nStatus: %d\n%s", receiptsHost, resp.StatusCode, string(body))
return impart.HTTPError{resp.StatusCode, string(body)}
}
return nil
}

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2018 A Bunch Tell LLC.
* Copyright © 2018-2019, 2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -12,8 +12,8 @@ package writefreely
import (
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/config"
"github.com/writefreely/go-nodeinfo"
"github.com/writefreely/writefreely/config"
"strings"
)
@ -45,7 +45,7 @@ func nodeInfoConfig(db *datastore, cfg *config.Config) *nodeinfo.Config {
Private: cfg.App.Private,
Software: nodeinfo.SoftwareMeta{
HomePage: softwareURL,
GitHub: "https://github.com/writeas/writefreely",
GitHub: "https://github.com/writefreely/writefreely",
Follow: "https://writing.exchange/@write_as",
},
MaxBlogs: cfg.App.MaxBlogs,
@ -94,14 +94,20 @@ INNER JOIN collections c
ON collection_id = c.id
WHERE collection_id IS NOT NULL
AND updated > DATE_SUB(NOW(), INTERVAL 6 MONTH)) co`).Scan(&activeHalfYear)
if err != nil {
log.Error("Failed getting 6-month active user stats: %s", err)
}
err = r.db.QueryRow(`SELECT COUNT(*) FROM (
SELECT DISTINCT collection_id
FROM posts
INNER JOIN FROM collections c
INNER JOIN collections c
ON collection_id = c.id
WHERE collection_id IS NOT NULL
AND updated > DATE_SUB(NOW(), INTERVAL 1 MONTH)) co`).Scan(&activeMonth)
if err != nil {
log.Error("Failed getting 1-month active user stats: %s", err)
}
}
return nodeinfo.Usage{

147
oauth.go
View File

@ -1,3 +1,13 @@
/*
* Copyright © 2019-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package writefreely
import (
@ -5,7 +15,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
@ -15,10 +24,35 @@ import (
"github.com/gorilla/sessions"
"github.com/writeas/impart"
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/config"
"github.com/writefreely/writefreely/config"
)
// OAuthButtons holds display information for different OAuth providers we support.
type OAuthButtons struct {
SlackEnabled bool
WriteAsEnabled bool
GitLabEnabled bool
GitLabDisplayName string
GiteaEnabled bool
GiteaDisplayName string
GenericEnabled bool
GenericDisplayName string
}
// NewOAuthButtons creates a new OAuthButtons struct based on our app configuration.
func NewOAuthButtons(cfg *config.Config) *OAuthButtons {
return &OAuthButtons{
SlackEnabled: cfg.SlackOauth.ClientID != "",
WriteAsEnabled: cfg.WriteAsOauth.ClientID != "",
GitLabEnabled: cfg.GitlabOauth.ClientID != "",
GitLabDisplayName: config.OrDefaultString(cfg.GitlabOauth.DisplayName, gitlabDisplayName),
GiteaEnabled: cfg.GiteaOauth.ClientID != "",
GiteaDisplayName: config.OrDefaultString(cfg.GiteaOauth.DisplayName, giteaDisplayName),
GenericEnabled: cfg.GenericOauth.ClientID != "",
GenericDisplayName: config.OrDefaultString(cfg.GenericOauth.DisplayName, genericOauthDisplayName),
}
}
// TokenResponse contains data returned when a token is created either
// through a code exchange or using a refresh token.
type TokenResponse struct {
@ -61,10 +95,10 @@ type OAuthDatastoreProvider interface {
type OAuthDatastore interface {
GetIDForRemoteUser(context.Context, string, string, string) (int64, error)
RecordRemoteUserID(context.Context, int64, string, string, string, string) error
ValidateOAuthState(context.Context, string) (string, string, int64, error)
GenerateOAuthState(context.Context, string, string, int64) (string, error)
ValidateOAuthState(context.Context, string) (string, string, int64, string, error)
GenerateOAuthState(context.Context, string, string, int64, string) (string, error)
CreateUser(*config.Config, *User, string) error
CreateUser(*config.Config, *User, string, string) error
GetUserByID(int64) (*User, error)
}
@ -108,7 +142,7 @@ func (h oauthHandler) viewOauthInit(app *App, w http.ResponseWriter, r *http.Req
attachUser = user.ID
}
state, err := h.DB.GenerateOAuthState(ctx, h.oauthClient.GetProvider(), h.oauthClient.GetClientID(), attachUser)
state, err := h.DB.GenerateOAuthState(ctx, h.oauthClient.GetProvider(), h.oauthClient.GetClientID(), attachUser, r.FormValue("invite_code"))
if err != nil {
log.Error("viewOauthInit error: %s", err)
return impart.HTTPError{http.StatusInternalServerError, "could not prepare oauth redirect url"}
@ -208,6 +242,70 @@ func configureGitlabOauth(parentHandler *Handler, r *mux.Router, app *App) {
}
}
func configureGenericOauth(parentHandler *Handler, r *mux.Router, app *App) {
if app.Config().GenericOauth.ClientID != "" {
callbackLocation := app.Config().App.Host + "/oauth/callback/generic"
var callbackProxy *callbackProxyClient = nil
if app.Config().GenericOauth.CallbackProxy != "" {
callbackProxy = &callbackProxyClient{
server: app.Config().GenericOauth.CallbackProxyAPI,
callbackLocation: app.Config().App.Host + "/oauth/callback/generic",
httpClient: config.DefaultHTTPClient(),
}
callbackLocation = app.Config().GenericOauth.CallbackProxy
}
oauthClient := genericOauthClient{
ClientID: app.Config().GenericOauth.ClientID,
ClientSecret: app.Config().GenericOauth.ClientSecret,
ExchangeLocation: app.Config().GenericOauth.Host + app.Config().GenericOauth.TokenEndpoint,
InspectLocation: app.Config().GenericOauth.Host + app.Config().GenericOauth.InspectEndpoint,
AuthLocation: app.Config().GenericOauth.Host + app.Config().GenericOauth.AuthEndpoint,
HttpClient: config.DefaultHTTPClient(),
CallbackLocation: callbackLocation,
Scope: config.OrDefaultString(app.Config().GenericOauth.Scope, "read_user"),
MapUserID: config.OrDefaultString(app.Config().GenericOauth.MapUserID, "user_id"),
MapUsername: config.OrDefaultString(app.Config().GenericOauth.MapUsername, "username"),
MapDisplayName: config.OrDefaultString(app.Config().GenericOauth.MapDisplayName, "-"),
MapEmail: config.OrDefaultString(app.Config().GenericOauth.MapEmail, "email"),
}
configureOauthRoutes(parentHandler, r, app, oauthClient, callbackProxy)
}
}
func configureGiteaOauth(parentHandler *Handler, r *mux.Router, app *App) {
if app.Config().GiteaOauth.ClientID != "" {
callbackLocation := app.Config().App.Host + "/oauth/callback/gitea"
var callbackProxy *callbackProxyClient = nil
if app.Config().GiteaOauth.CallbackProxy != "" {
callbackProxy = &callbackProxyClient{
server: app.Config().GiteaOauth.CallbackProxyAPI,
callbackLocation: app.Config().App.Host + "/oauth/callback/gitea",
httpClient: config.DefaultHTTPClient(),
}
callbackLocation = app.Config().GiteaOauth.CallbackProxy
}
oauthClient := giteaOauthClient{
ClientID: app.Config().GiteaOauth.ClientID,
ClientSecret: app.Config().GiteaOauth.ClientSecret,
ExchangeLocation: app.Config().GiteaOauth.Host + "/login/oauth/access_token",
InspectLocation: app.Config().GiteaOauth.Host + "/login/oauth/userinfo",
AuthLocation: app.Config().GiteaOauth.Host + "/login/oauth/authorize",
HttpClient: config.DefaultHTTPClient(),
CallbackLocation: callbackLocation,
Scope: "openid profile email",
MapUserID: "sub",
MapUsername: "login",
MapDisplayName: "full_name",
MapEmail: "email",
}
configureOauthRoutes(parentHandler, r, app, oauthClient, callbackProxy)
}
}
func configureOauthRoutes(parentHandler *Handler, r *mux.Router, app *App, oauthClient oauthClient, callbackProxy *callbackProxyClient) {
handler := &oauthHandler{
Config: app.Config(),
@ -228,7 +326,7 @@ func (h oauthHandler) viewOauthCallback(app *App, w http.ResponseWriter, r *http
code := r.FormValue("code")
state := r.FormValue("state")
provider, clientID, attachUserID, err := h.DB.ValidateOAuthState(ctx, state)
provider, clientID, attachUserID, inviteCode, err := h.DB.ValidateOAuthState(ctx, state)
if err != nil {
log.Error("Unable to ValidateOAuthState: %s", err)
return impart.HTTPError{http.StatusInternalServerError, err.Error()}
@ -237,10 +335,16 @@ func (h oauthHandler) viewOauthCallback(app *App, w http.ResponseWriter, r *http
tokenResponse, err := h.oauthClient.exchangeOauthCode(ctx, code)
if err != nil {
log.Error("Unable to exchangeOauthCode: %s", err)
// TODO: show user friendly message if needed
// TODO: show NO message for cases like user pressing "Cancel" on authorize step
addSessionFlash(app, w, r, err.Error(), nil)
if attachUserID > 0 {
return impart.HTTPError{http.StatusFound, "/me/settings"}
}
return impart.HTTPError{http.StatusInternalServerError, err.Error()}
}
// Now that we have the access token, let's use it real quick to make sur
// Now that we have the access token, let's use it real quick to make sure
// it really really works.
tokenInfo, err := h.oauthClient.inspectOauthAccessToken(ctx, tokenResponse.AccessToken)
if err != nil {
@ -255,13 +359,14 @@ func (h oauthHandler) viewOauthCallback(app *App, w http.ResponseWriter, r *http
}
if localUserID != -1 && attachUserID > 0 {
if err = addSessionFlash(app, w, r, "This Slack account is already attached to another user.", nil); err != nil {
if err = addSessionFlash(app, w, r, "This OAuth account is already attached to another user.", nil); err != nil {
return impart.HTTPError{Status: http.StatusInternalServerError, Message: err.Error()}
}
return impart.HTTPError{http.StatusFound, "/me/settings"}
}
if localUserID != -1 {
// Existing user, so log in now
user, err := h.DB.GetUserByID(localUserID)
if err != nil {
log.Error("Unable to GetUserByID %d: %s", localUserID, err)
@ -275,6 +380,7 @@ func (h oauthHandler) viewOauthCallback(app *App, w http.ResponseWriter, r *http
}
if attachUserID > 0 {
log.Info("attaching to user %d", attachUserID)
log.Info("OAuth userid: %s", tokenInfo.UserID)
err = h.DB.RecordRemoteUserID(r.Context(), attachUserID, tokenInfo.UserID, provider, clientID, tokenResponse.AccessToken)
if err != nil {
return impart.HTTPError{http.StatusInternalServerError, err.Error()}
@ -282,6 +388,22 @@ func (h oauthHandler) viewOauthCallback(app *App, w http.ResponseWriter, r *http
return impart.HTTPError{http.StatusFound, "/me/settings"}
}
// New user registration below.
// First, verify that user is allowed to register
if inviteCode != "" {
// Verify invite code is valid
i, err := app.db.GetUserInvite(inviteCode)
if err != nil {
return impart.HTTPError{http.StatusInternalServerError, err.Error()}
}
if !i.Active(app.db) {
return impart.HTTPError{http.StatusNotFound, "Invite link has expired."}
}
} else if !app.cfg.App.OpenRegistration {
addSessionFlash(app, w, r, ErrUserNotFound.Error(), nil)
return impart.HTTPError{http.StatusFound, "/login"}
}
displayName := tokenInfo.DisplayName
if len(displayName) == 0 {
displayName = tokenInfo.Username
@ -295,6 +417,7 @@ func (h oauthHandler) viewOauthCallback(app *App, w http.ResponseWriter, r *http
TokenRemoteUser: tokenInfo.UserID,
Provider: provider,
ClientID: clientID,
InviteCode: inviteCode,
}
tp.TokenHash = tp.HashTokenParams(h.Config.Server.HashSeed)
@ -309,7 +432,7 @@ func (r *callbackProxyClient) register(ctx context.Context, state string) error
if err != nil {
return err
}
req.Header.Set("User-Agent", "writefreely")
req.Header.Set("User-Agent", ServerUserAgent(""))
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
@ -326,7 +449,7 @@ func (r *callbackProxyClient) register(ctx context.Context, state string) error
func limitedJsonUnmarshal(body io.ReadCloser, n int, thing interface{}) error {
lr := io.LimitReader(body, int64(n+1))
data, err := ioutil.ReadAll(lr)
data, err := io.ReadAll(lr)
if err != nil {
return err
}

View File

@ -7,4 +7,3 @@ type ClientStateStore interface {
Generate(ctx context.Context) (string, error)
Validate(ctx context.Context, state string) error
}

142
oauth_generic.go Normal file
View File

@ -0,0 +1,142 @@
/*
* Copyright © 2020-2021 Musing Studio LLC and respective authors.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package writefreely
import (
"context"
"errors"
"fmt"
"github.com/writeas/web-core/log"
"net/http"
"net/url"
"strings"
)
type genericOauthClient struct {
ClientID string
ClientSecret string
AuthLocation string
ExchangeLocation string
InspectLocation string
CallbackLocation string
Scope string
MapUserID string
MapUsername string
MapDisplayName string
MapEmail string
HttpClient HttpClient
}
var _ oauthClient = genericOauthClient{}
const (
genericOauthDisplayName = "OAuth"
)
func (c genericOauthClient) GetProvider() string {
return "generic"
}
func (c genericOauthClient) GetClientID() string {
return c.ClientID
}
func (c genericOauthClient) GetCallbackLocation() string {
return c.CallbackLocation
}
func (c genericOauthClient) buildLoginURL(state string) (string, error) {
u, err := url.Parse(c.AuthLocation)
if err != nil {
return "", err
}
q := u.Query()
q.Set("client_id", c.ClientID)
q.Set("redirect_uri", c.CallbackLocation)
q.Set("response_type", "code")
q.Set("state", state)
q.Set("scope", c.Scope)
u.RawQuery = q.Encode()
return u.String(), nil
}
func (c genericOauthClient) exchangeOauthCode(ctx context.Context, code string) (*TokenResponse, error) {
form := url.Values{}
form.Add("grant_type", "authorization_code")
form.Add("redirect_uri", c.CallbackLocation)
form.Add("scope", c.Scope)
form.Add("code", code)
req, err := http.NewRequest("POST", c.ExchangeLocation, strings.NewReader(form.Encode()))
if err != nil {
return nil, err
}
req.WithContext(ctx)
req.Header.Set("User-Agent", ServerUserAgent(""))
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(c.ClientID, c.ClientSecret)
resp, err := c.HttpClient.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, errors.New("unable to exchange code for access token")
}
var tokenResponse TokenResponse
if err := limitedJsonUnmarshal(resp.Body, tokenRequestMaxLen, &tokenResponse); err != nil {
return nil, err
}
if tokenResponse.Error != "" {
return nil, errors.New(tokenResponse.Error)
}
return &tokenResponse, nil
}
func (c genericOauthClient) inspectOauthAccessToken(ctx context.Context, accessToken string) (*InspectResponse, error) {
req, err := http.NewRequest("GET", c.InspectLocation, nil)
if err != nil {
return nil, err
}
req.WithContext(ctx)
req.Header.Set("User-Agent", ServerUserAgent(""))
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", "Bearer "+accessToken)
resp, err := c.HttpClient.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, errors.New("unable to inspect access token")
}
// since we don't know what the JSON from the server will look like, we create a
// generic interface and then map manually to values set in the config
var genericInterface map[string]interface{}
if err := limitedJsonUnmarshal(resp.Body, infoRequestMaxLen, &genericInterface); err != nil {
return nil, err
}
// map each relevant field in inspectResponse to the mapped field from the config
var inspectResponse InspectResponse
inspectResponse.UserID, _ = genericInterface[c.MapUserID].(string)
if inspectResponse.UserID == "" {
log.Error("[CONFIGURATION ERROR] Generic OAuth provider returned empty UserID value (`%s`).\n Do you need to configure a different `map_user_id` value for this provider?", c.MapUserID)
return nil, fmt.Errorf("no UserID (`%s`) value returned", c.MapUserID)
}
inspectResponse.Username, _ = genericInterface[c.MapUsername].(string)
inspectResponse.DisplayName, _ = genericInterface[c.MapDisplayName].(string)
inspectResponse.Email, _ = genericInterface[c.MapEmail].(string)
return &inspectResponse, nil
}

133
oauth_gitea.go Normal file
View File

@ -0,0 +1,133 @@
package writefreely
import (
"context"
"errors"
"fmt"
"github.com/writeas/web-core/log"
"net/http"
"net/url"
"strings"
)
type giteaOauthClient struct {
ClientID string
ClientSecret string
AuthLocation string
ExchangeLocation string
InspectLocation string
CallbackLocation string
Scope string
MapUserID string
MapUsername string
MapDisplayName string
MapEmail string
HttpClient HttpClient
}
var _ oauthClient = giteaOauthClient{}
const (
giteaDisplayName = "Gitea"
)
func (c giteaOauthClient) GetProvider() string {
return "gitea"
}
func (c giteaOauthClient) GetClientID() string {
return c.ClientID
}
func (c giteaOauthClient) GetCallbackLocation() string {
return c.CallbackLocation
}
func (c giteaOauthClient) buildLoginURL(state string) (string, error) {
u, err := url.Parse(c.AuthLocation)
if err != nil {
return "", err
}
q := u.Query()
q.Set("client_id", c.ClientID)
q.Set("redirect_uri", c.CallbackLocation)
q.Set("response_type", "code")
q.Set("state", state)
q.Set("scope", c.Scope)
u.RawQuery = q.Encode()
return u.String(), nil
}
func (c giteaOauthClient) exchangeOauthCode(ctx context.Context, code string) (*TokenResponse, error) {
form := url.Values{}
form.Add("grant_type", "authorization_code")
form.Add("redirect_uri", c.CallbackLocation)
form.Add("scope", c.Scope)
form.Add("code", code)
req, err := http.NewRequest("POST", c.ExchangeLocation, strings.NewReader(form.Encode()))
if err != nil {
return nil, err
}
req.WithContext(ctx)
req.Header.Set("User-Agent", ServerUserAgent(""))
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(c.ClientID, c.ClientSecret)
resp, err := c.HttpClient.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, errors.New("unable to exchange code for access token")
}
var tokenResponse TokenResponse
if err := limitedJsonUnmarshal(resp.Body, tokenRequestMaxLen, &tokenResponse); err != nil {
return nil, err
}
if tokenResponse.Error != "" {
return nil, errors.New(tokenResponse.Error)
}
return &tokenResponse, nil
}
func (c giteaOauthClient) inspectOauthAccessToken(ctx context.Context, accessToken string) (*InspectResponse, error) {
req, err := http.NewRequest("GET", c.InspectLocation, nil)
if err != nil {
return nil, err
}
req.WithContext(ctx)
req.Header.Set("User-Agent", ServerUserAgent(""))
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", "Bearer "+accessToken)
resp, err := c.HttpClient.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, errors.New("unable to inspect access token")
}
// since we don't know what the JSON from the server will look like, we create a
// generic interface and then map manually to values set in the config
var genericInterface map[string]interface{}
if err := limitedJsonUnmarshal(resp.Body, infoRequestMaxLen, &genericInterface); err != nil {
return nil, err
}
// map each relevant field in inspectResponse to the mapped field from the config
var inspectResponse InspectResponse
inspectResponse.UserID, _ = genericInterface[c.MapUserID].(string)
// log.Info("Userid from Gitea: %s", inspectResponse.UserID)
if inspectResponse.UserID == "" {
log.Error("[CONFIGURATION ERROR] Gitea OAuth provider returned empty UserID value (`%s`).\n Do you need to configure a different `map_user_id` value for this provider?", c.MapUserID)
return nil, fmt.Errorf("no UserID (`%s`) value returned", c.MapUserID)
}
inspectResponse.Username, _ = genericInterface[c.MapUsername].(string)
inspectResponse.DisplayName, _ = genericInterface[c.MapDisplayName].(string)
inspectResponse.Email, _ = genericInterface[c.MapEmail].(string)
return &inspectResponse, nil
}

View File

@ -63,7 +63,7 @@ func (c gitlabOauthClient) exchangeOauthCode(ctx context.Context, code string) (
return nil, err
}
req.WithContext(ctx)
req.Header.Set("User-Agent", "writefreely")
req.Header.Set("User-Agent", ServerUserAgent(""))
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(c.ClientID, c.ClientSecret)
@ -92,7 +92,7 @@ func (c gitlabOauthClient) inspectOauthAccessToken(ctx context.Context, accessTo
return nil, err
}
req.WithContext(ctx)
req.Header.Set("User-Agent", "writefreely")
req.Header.Set("User-Agent", ServerUserAgent(""))
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", "Bearer "+accessToken)

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2020 A Bunch Tell LLC.
* Copyright © 2020-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -17,7 +17,7 @@ import (
"github.com/writeas/impart"
"github.com/writeas/web-core/auth"
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/page"
"github.com/writefreely/writefreely/page"
"html/template"
"net/http"
"strings"
@ -38,6 +38,7 @@ type viewOauthSignupVars struct {
Provider string
ClientID string
TokenHash string
InviteCode string
LoginUsername string
Alias string // TODO: rename this to match the data it represents: the collection title
@ -57,6 +58,7 @@ const (
oauthParamAlias = "alias"
oauthParamEmail = "email"
oauthParamPassword = "password"
oauthParamInviteCode = "invite_code"
)
type oauthSignupPageParams struct {
@ -68,6 +70,7 @@ type oauthSignupPageParams struct {
ClientID string
Provider string
TokenHash string
InviteCode string
}
func (p oauthSignupPageParams) HashTokenParams(key string) string {
@ -92,6 +95,7 @@ func (h oauthHandler) viewOauthSignup(app *App, w http.ResponseWriter, r *http.R
TokenRemoteUser: r.FormValue(oauthParamTokenRemoteUserID),
ClientID: r.FormValue(oauthParamClientID),
Provider: r.FormValue(oauthParamProvider),
InviteCode: r.FormValue(oauthParamInviteCode),
}
if tp.HashTokenParams(h.Config.Server.HashSeed) != r.FormValue(oauthParamHash) {
return impart.HTTPError{Status: http.StatusBadRequest, Message: "Request has been tampered with."}
@ -123,11 +127,19 @@ func (h oauthHandler) viewOauthSignup(app *App, w http.ResponseWriter, r *http.R
displayName = r.FormValue(oauthParamUsername)
}
err = h.DB.CreateUser(h.Config, newUser, displayName)
err = h.DB.CreateUser(h.Config, newUser, displayName, "")
if err != nil {
return h.showOauthSignupPage(app, w, r, tp, err)
}
// Log invite if needed
if tp.InviteCode != "" {
err = app.db.CreateInvitedUser(tp.InviteCode, newUser.ID)
if err != nil {
return err
}
}
err = h.DB.RecordRemoteUserID(r.Context(), newUser.ID, r.FormValue(oauthParamTokenRemoteUserID), r.FormValue(oauthParamProvider), r.FormValue(oauthParamClientID), r.FormValue(oauthParamAccessToken))
if err != nil {
return h.showOauthSignupPage(app, w, r, tp, err)
@ -195,6 +207,7 @@ func (h oauthHandler) showOauthSignupPage(app *App, w http.ResponseWriter, r *ht
Provider: tp.Provider,
ClientID: tp.ClientID,
TokenHash: tp.TokenHash,
InviteCode: tp.InviteCode,
LoginUsername: username,
Alias: collTitle,

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2019-2020 A Bunch Tell LLC.
* Copyright © 2019-2020 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
@ -13,8 +13,6 @@ package writefreely
import (
"context"
"errors"
"fmt"
"github.com/writeas/nerds/store"
"github.com/writeas/slug"
"net/http"
"net/url"
@ -113,7 +111,7 @@ func (c slackOauthClient) exchangeOauthCode(ctx context.Context, code string) (*
return nil, err
}
req.WithContext(ctx)
req.Header.Set("User-Agent", "writefreely")
req.Header.Set("User-Agent", ServerUserAgent(""))
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(c.ClientID, c.ClientSecret)
@ -142,7 +140,7 @@ func (c slackOauthClient) inspectOauthAccessToken(ctx context.Context, accessTok
return nil, err
}
req.WithContext(ctx)
req.Header.Set("User-Agent", "writefreely")
req.Header.Set("User-Agent", ServerUserAgent(""))
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", "Bearer "+accessToken)
@ -167,7 +165,7 @@ func (c slackOauthClient) inspectOauthAccessToken(ctx context.Context, accessTok
func (resp slackUserIdentityResponse) InspectResponse() *InspectResponse {
return &InspectResponse{
UserID: resp.User.ID,
Username: fmt.Sprintf("%s-%s", slug.Make(resp.User.Name), store.GenerateRandomString("0123456789bcdfghjklmnpqrstvwxyz", 5)),
Username: slug.Make(resp.User.Name),
DisplayName: resp.User.Name,
Email: resp.User.Email,
}

View File

@ -1,3 +1,13 @@
/*
* Copyright © 2019-2021 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package writefreely
import (
@ -6,8 +16,8 @@ import (
"github.com/gorilla/sessions"
"github.com/stretchr/testify/assert"
"github.com/writeas/impart"
"github.com/writeas/nerds/store"
"github.com/writeas/writefreely/config"
"github.com/writeas/web-core/id"
"github.com/writefreely/writefreely/config"
"net/http"
"net/http/httptest"
"net/url"
@ -22,8 +32,8 @@ type MockOAuthDatastoreProvider struct {
}
type MockOAuthDatastore struct {
DoGenerateOAuthState func(context.Context, string, string, int64) (string, error)
DoValidateOAuthState func(context.Context, string) (string, string, int64, error)
DoGenerateOAuthState func(context.Context, string, string, int64, string) (string, error)
DoValidateOAuthState func(context.Context, string) (string, string, int64, string, error)
DoGetIDForRemoteUser func(context.Context, string, string, string) (int64, error)
DoCreateUser func(*config.Config, *User, string) error
DoRecordRemoteUserID func(context.Context, int64, string, string, string, string) error
@ -86,11 +96,11 @@ func (m *MockOAuthDatastoreProvider) Config() *config.Config {
return cfg
}
func (m *MockOAuthDatastore) ValidateOAuthState(ctx context.Context, state string) (string, string, int64, error) {
func (m *MockOAuthDatastore) ValidateOAuthState(ctx context.Context, state string) (string, string, int64, string, error) {
if m.DoValidateOAuthState != nil {
return m.DoValidateOAuthState(ctx, state)
}
return "", "", 0, nil
return "", "", 0, "", nil
}
func (m *MockOAuthDatastore) GetIDForRemoteUser(ctx context.Context, remoteUserID, provider, clientID string) (int64, error) {
@ -100,7 +110,7 @@ func (m *MockOAuthDatastore) GetIDForRemoteUser(ctx context.Context, remoteUserI
return -1, nil
}
func (m *MockOAuthDatastore) CreateUser(cfg *config.Config, u *User, username string) error {
func (m *MockOAuthDatastore) CreateUser(cfg *config.Config, u *User, username, description string) error {
if m.DoCreateUser != nil {
return m.DoCreateUser(cfg, u, username)
}
@ -119,17 +129,15 @@ func (m *MockOAuthDatastore) GetUserByID(userID int64) (*User, error) {
if m.DoGetUserByID != nil {
return m.DoGetUserByID(userID)
}
user := &User{
}
user := &User{}
return user, nil
}
func (m *MockOAuthDatastore) GenerateOAuthState(ctx context.Context, provider string, clientID string, attachUserID int64) (string, error) {
func (m *MockOAuthDatastore) GenerateOAuthState(ctx context.Context, provider string, clientID string, attachUserID int64, inviteCode string) (string, error) {
if m.DoGenerateOAuthState != nil {
return m.DoGenerateOAuthState(ctx, provider, clientID, attachUserID)
return m.DoGenerateOAuthState(ctx, provider, clientID, attachUserID, inviteCode)
}
return store.Generate62RandomString(14), nil
return id.Generate62RandomString(14), nil
}
func TestViewOauthInit(t *testing.T) {
@ -173,7 +181,7 @@ func TestViewOauthInit(t *testing.T) {
app := &MockOAuthDatastoreProvider{
DoDB: func() OAuthDatastore {
return &MockOAuthDatastore{
DoGenerateOAuthState: func(ctx context.Context, provider, clientID string, attachUserID int64) (string, error) {
DoGenerateOAuthState: func(ctx context.Context, provider, clientID string, attachUserID int64, inviteCode string) (string, error) {
return "", fmt.Errorf("pretend unable to write state error")
},
}
@ -246,7 +254,7 @@ func TestViewOauthCallback(t *testing.T) {
req, err := http.NewRequest("GET", "/oauth/callback", nil)
assert.NoError(t, err)
rr := httptest.NewRecorder()
err = h.viewOauthCallback(nil, rr, req)
err = h.viewOauthCallback(&App{cfg: app.Config(), sessionStore: app.SessionStore()}, rr, req)
assert.NoError(t, err)
assert.Equal(t, http.StatusTemporaryRedirect, rr.Code)
})

View File

@ -62,7 +62,7 @@ func (c writeAsOauthClient) exchangeOauthCode(ctx context.Context, code string)
return nil, err
}
req.WithContext(ctx)
req.Header.Set("User-Agent", "writefreely")
req.Header.Set("User-Agent", ServerUserAgent(""))
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(c.ClientID, c.ClientSecret)
@ -91,7 +91,7 @@ func (c writeAsOauthClient) inspectOauthAccessToken(ctx context.Context, accessT
return nil, err
}
req.WithContext(ctx)
req.Header.Set("User-Agent", "writefreely")
req.Header.Set("User-Agent", ServerUserAgent(""))
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", "Bearer "+accessToken)

10
ossl_legacy.cnf Normal file
View File

@ -0,0 +1,10 @@
[provider_sect]
default = default_sect
legacy = legacy_sect
[default_sect]
activate = 1
[legacy_sect]
activate = 1

Some files were not shown because too many files have changed in this diff Show More