[feature] Allow users to set custom css for their profiles + threads (#808)

* add custom css account property + db func to fetch

* allow account to get/set custom css

* serve custom css for an account

* go fmt

* use monospace for customcss, add link

* add custom css to account cache

* fix broken field

* add custom css docs to user guide

* add `accounts-allow-custom-css` config flag

* add allow custom css to /api/v1/instance response

* only show/set custom css if allowed to do so

* only set/serve custom account css if enabled

* update swagger docs

* chain promise

* make bool a bit clearer

* use cache for GetAccountCustomCSSByUsername
This commit is contained in:
tobi 2022-09-12 13:14:29 +02:00 committed by GitHub
parent 268f252e0d
commit b42469e4e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 458 additions and 39 deletions

View File

@ -12,6 +12,17 @@ definitions:
title: A FileHeader describes a file part of a multipart request. title: A FileHeader describes a file part of a multipart request.
type: object type: object
x-go-package: mime/multipart x-go-package: mime/multipart
InstanceConfigurationAccounts:
properties:
allow_custom_css:
description: Whether or not accounts on this instance are allowed to upload
custom CSS for profiles and statuses.
example: false
type: boolean
x-go-name: AllowCustomCSS
title: InstanceConfigurationAccounts models instance account config parameters.
type: object
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
Link: Link:
description: See https://webfinger.net/ description: See https://webfinger.net/
properties: properties:
@ -240,6 +251,11 @@ definitions:
example: "2021-07-30T09:20:25+00:00" example: "2021-07-30T09:20:25+00:00"
type: string type: string
x-go-name: CreatedAt x-go-name: CreatedAt
custom_css:
description: CustomCSS to include when rendering this account's profile or
statuses.
type: string
x-go-name: CustomCSS
discoverable: discoverable:
description: Account has opted into discovery features. description: Account has opted into discovery features.
type: boolean type: boolean
@ -1086,6 +1102,8 @@ definitions:
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
instanceConfiguration: instanceConfiguration:
properties: properties:
accounts:
$ref: '#/definitions/InstanceConfigurationAccounts'
media_attachments: media_attachments:
$ref: '#/definitions/instanceConfigurationMediaAttachments' $ref: '#/definitions/instanceConfigurationMediaAttachments'
polls: polls:
@ -2618,6 +2636,12 @@ paths:
in: formData in: formData
name: source[status_format] name: source[status_format]
type: string type: string
- description: |-
Custom CSS to use when rendering this account's profile or statuses.
String must be no more than 5,000 characters (~5kb).
in: formData
name: custom_css
type: string
produces: produces:
- application/json - application/json
responses: responses:

BIN
docs/assets/cssblack.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 KiB

BIN
docs/assets/cssgradient.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

BIN
docs/assets/cssstandard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

View File

@ -23,4 +23,20 @@ accounts-approval-required: true
# Options: [true, false] # Options: [true, false]
# Default: true # Default: true
accounts-reason-required: true accounts-reason-required: true
# Bool. Allow accounts on this instance to set custom CSS for their profile pages and statuses.
# Enabling this setting will allow accounts to upload custom CSS via the /user settings page,
# which will then be rendered on the web view of the account's profile and statuses.
#
# For instances with public sign ups, it is **HIGHLY RECOMMENDED** to leave this setting on 'false',
# since setting it to true allows malicious accounts to make their profile pages misleading, unusable
# or even dangerous to visitors. In other words, you should only enable this setting if you trust
# the users on your instance not to produce harmful CSS.
#
# Regardless of what this value is set to, any uploaded CSS will not be federated to other instances,
# it will only be shown on profiles and statuses on *this* instance.
#
# Options: [true, false]
# Default: false
accounts-allow-custom-css: false
``` ```

View File

@ -0,0 +1,73 @@
# Custom CSS (Advanced)
CSS (Cascading Style Sheets) is a coding language used alongside HTML, which determines how a web page looks in a web browser:
> While HTML is used to define the structure and semantics of your content, CSS is used to style it and lay it out. For example, you can use CSS to alter the font, color, size, and spacing of your content, split it into multiple columns, or add animations and other decorative features.
>
> -- [Learn CSS (Mozilla)](https://developer.mozilla.org/en-US/docs/Learn/CSS)
Depending on the settings configured by the admin of your GoToSocial instance, you may be able to upload custom CSS for your account via the User Settings Panel.
This allows you to customize the appearance of your GoToSocial profile for users visiting it using a web browser.
## Example - Changing Background Color
Here's a standard GoToSocial profile page:
![A GoToSocial test profile page. The standard color scheme of grey, blue, and orange.](./../assets/cssstandard.png)
Let's say we want the background color to be black instead of grey.
In the User Settings Panel, we enter the following CSS code in the Custom CSS field:
```css
.page {
background: black;
}
```
We then click on Save Profile Info.
If we go back to our profile page and refresh the page, it now looks like this:
![The same GoToSocial test profile page. The background is now black.](./../assets/cssblack.png)
If we want to get really fancy, we can add an ombre effect to the background, by using the following CSS code instead:
```css
.page {
background: linear-gradient(crimson, purple);
}
```
After saving the css and refreshing the profile page, the profile now looks like this:
![The same GoToSocial test profile page. The background now starts dark red and fades to purple further down the page.](./../assets/cssgradient.png)
## Accessibility
The importance of accessible HTML and CSS cannot be overstated. From W3:
> The Web is fundamentally designed to work for all people, whatever their hardware, software, language, location, or ability. When the Web meets this goal, it is accessible to people with a diverse range of hearing, movement, sight, and cognitive ability.
>
> Thus the impact of disability is radically changed on the Web because the Web removes barriers to communication and interaction that many people face in the physical world. However, when websites, applications, technologies, or tools are badly designed, they can create barriers that exclude people from using the Web.
>
> Accessibility is essential for developers and organizations that want to create high-quality websites and web tools, and not exclude people from using their products and services.
>
> -- [Introduction To Web Accessibility](https://www.w3.org/WAI/fundamentals/accessibility-intro/)
The standard GoToSocial theme is designed with web accessibility in mind, especially when it comes to layout, color contrasts, font sizes, and so on.
If you write custom CSS for your profile, it is very important that you make sure that it remains legible and that it behaves as expected. Buttons should look like buttons, links should look like links, text should be presented in a readable font, elements should not jump around the page, etc. Web pages can be pretty and exciting without sacrificing readability, or making things overcomplicated.
If you change your color scheme, it's a good idea to validate the new colors to make sure that they have sufficient contrast to be readable by people with visual impairments like color blindness. Once you've updated your CSS, try entering your profile URL in a contrast checking tool, like the [Color Contrast Accessibility Validator](https://color.a11y.com/Contrast). You can also use the 'Accessibility' tab in the developer tools of your web browser to check for any issues.
Styling with accessibility in mind makes the web better for everyone! Have a look at the links below for more information.
## Useful Links
- [Learn CSS (Mozilla)](https://developer.mozilla.org/en-US/docs/Learn/CSS)
- [CSS Tutorial (W3 Schools)](https://www.w3schools.com/Css/default.asp)
- [CSS and JavaScript Accessibility Best Practices (Mozilla)](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/CSS_and_JavaScript#css)
- [WAVE Web Accessibility Evaluation Tool](https://wave.webaim.org/)
- [Color Contrast Accessibility Validator](https://color.a11y.com/Contrast)

View File

@ -207,6 +207,22 @@ accounts-approval-required: true
# Default: true # Default: true
accounts-reason-required: true accounts-reason-required: true
# Bool. Allow accounts on this instance to set custom CSS for their profile pages and statuses.
# Enabling this setting will allow accounts to upload custom CSS via the /user settings page,
# which will then be rendered on the web view of the account's profile and statuses.
#
# For instances with public sign ups, it is **HIGHLY RECOMMENDED** to leave this setting on 'false',
# since setting it to true allows malicious accounts to make their profile pages misleading, unusable
# or even dangerous to visitors. In other words, you should only enable this setting if you trust
# the users on your instance not to produce harmful CSS.
#
# Regardless of what this value is set to, any uploaded CSS will not be federated to other instances,
# it will only be shown on profiles and statuses on *this* instance.
#
# Options: [true, false]
# Default: false
accounts-allow-custom-css: false
######################## ########################
##### MEDIA CONFIG ##### ##### MEDIA CONFIG #####
######################## ########################

View File

@ -92,6 +92,12 @@
// in: formData // in: formData
// description: Default format to use for authored statuses (plain or markdown). // description: Default format to use for authored statuses (plain or markdown).
// type: string // type: string
// - name: custom_css
// in: formData
// description: |-
// Custom CSS to use when rendering this account's profile or statuses.
// String must be no more than 5,000 characters (~5kb).
// type: string
// //
// security: // security:
// - OAuth2 Bearer: // - OAuth2 Bearer:
@ -183,7 +189,8 @@ func parseUpdateAccountForm(c *gin.Context) (*model.UpdateCredentialsRequest, er
form.Source.Sensitive == nil && form.Source.Sensitive == nil &&
form.Source.Language == nil && form.Source.Language == nil &&
form.Source.StatusFormat == nil && form.Source.StatusFormat == nil &&
form.FieldsAttributes == nil) { form.FieldsAttributes == nil &&
form.CustomCSS == nil) {
return nil, errors.New("empty form submitted") return nil, errors.New("empty form submitted")
} }

View File

@ -63,7 +63,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() {
b, err := io.ReadAll(result.Body) b, err := io.ReadAll(result.Body)
suite.NoError(err) suite.NoError(err)
suite.Equal(`{"uri":"http://localhost:8080","account_domain":"localhost:8080","title":"Example Instance","description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","short_description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","email":"someone@example.org","version":"0.0.0-testrig","registrations":true,"approval_required":true,"invites_enabled":false,"configuration":{"statuses":{"max_characters":5000,"max_media_attachments":6,"characters_reserved_per_url":25},"media_attachments":{"supported_mime_types":["image/jpeg","image/gif","image/png"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":16777216},"polls":{"max_options":6,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":2,"status_count":16,"user_count":4},"thumbnail":"http://localhost:8080/assets/logo.png","contact_account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"max_toot_chars":5000}`, string(b)) suite.Equal(`{"uri":"http://localhost:8080","account_domain":"localhost:8080","title":"Example Instance","description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","short_description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","email":"someone@example.org","version":"0.0.0-testrig","registrations":true,"approval_required":true,"invites_enabled":false,"configuration":{"statuses":{"max_characters":5000,"max_media_attachments":6,"characters_reserved_per_url":25},"media_attachments":{"supported_mime_types":["image/jpeg","image/gif","image/png"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":16777216},"polls":{"max_options":6,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746},"accounts":{"allow_custom_css":true}},"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":2,"status_count":16,"user_count":4},"thumbnail":"http://localhost:8080/assets/logo.png","contact_account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"max_toot_chars":5000}`, string(b))
} }
func (suite *InstancePatchTestSuite) TestInstancePatch2() { func (suite *InstancePatchTestSuite) TestInstancePatch2() {
@ -93,7 +93,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() {
b, err := io.ReadAll(result.Body) b, err := io.ReadAll(result.Body)
suite.NoError(err) suite.NoError(err)
suite.Equal(`{"uri":"http://localhost:8080","account_domain":"localhost:8080","title":"Geoff's Instance","description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","short_description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","email":"admin@example.org","version":"0.0.0-testrig","registrations":true,"approval_required":true,"invites_enabled":false,"configuration":{"statuses":{"max_characters":5000,"max_media_attachments":6,"characters_reserved_per_url":25},"media_attachments":{"supported_mime_types":["image/jpeg","image/gif","image/png"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":16777216},"polls":{"max_options":6,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":2,"status_count":16,"user_count":4},"thumbnail":"http://localhost:8080/assets/logo.png","contact_account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"max_toot_chars":5000}`, string(b)) suite.Equal(`{"uri":"http://localhost:8080","account_domain":"localhost:8080","title":"Geoff's Instance","description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","short_description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","email":"admin@example.org","version":"0.0.0-testrig","registrations":true,"approval_required":true,"invites_enabled":false,"configuration":{"statuses":{"max_characters":5000,"max_media_attachments":6,"characters_reserved_per_url":25},"media_attachments":{"supported_mime_types":["image/jpeg","image/gif","image/png"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":16777216},"polls":{"max_options":6,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746},"accounts":{"allow_custom_css":true}},"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":2,"status_count":16,"user_count":4},"thumbnail":"http://localhost:8080/assets/logo.png","contact_account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"max_toot_chars":5000}`, string(b))
} }
func (suite *InstancePatchTestSuite) TestInstancePatch3() { func (suite *InstancePatchTestSuite) TestInstancePatch3() {
@ -123,7 +123,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() {
b, err := io.ReadAll(result.Body) b, err := io.ReadAll(result.Body)
suite.NoError(err) suite.NoError(err)
suite.Equal(`{"uri":"http://localhost:8080","account_domain":"localhost:8080","title":"GoToSocial Testrig Instance","description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","short_description":"\u003cp\u003eThis is some html, which is \u003cem\u003eallowed\u003c/em\u003e in short descriptions.\u003c/p\u003e","email":"admin@example.org","version":"0.0.0-testrig","registrations":true,"approval_required":true,"invites_enabled":false,"configuration":{"statuses":{"max_characters":5000,"max_media_attachments":6,"characters_reserved_per_url":25},"media_attachments":{"supported_mime_types":["image/jpeg","image/gif","image/png"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":16777216},"polls":{"max_options":6,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":2,"status_count":16,"user_count":4},"thumbnail":"http://localhost:8080/assets/logo.png","contact_account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"max_toot_chars":5000}`, string(b)) suite.Equal(`{"uri":"http://localhost:8080","account_domain":"localhost:8080","title":"GoToSocial Testrig Instance","description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","short_description":"\u003cp\u003eThis is some html, which is \u003cem\u003eallowed\u003c/em\u003e in short descriptions.\u003c/p\u003e","email":"admin@example.org","version":"0.0.0-testrig","registrations":true,"approval_required":true,"invites_enabled":false,"configuration":{"statuses":{"max_characters":5000,"max_media_attachments":6,"characters_reserved_per_url":25},"media_attachments":{"supported_mime_types":["image/jpeg","image/gif","image/png"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":16777216},"polls":{"max_options":6,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746},"accounts":{"allow_custom_css":true}},"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":2,"status_count":16,"user_count":4},"thumbnail":"http://localhost:8080/assets/logo.png","contact_account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"max_toot_chars":5000}`, string(b))
} }
func (suite *InstancePatchTestSuite) TestInstancePatch4() { func (suite *InstancePatchTestSuite) TestInstancePatch4() {
@ -214,7 +214,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() {
b, err := io.ReadAll(result.Body) b, err := io.ReadAll(result.Body)
suite.NoError(err) suite.NoError(err)
suite.Equal(`{"uri":"http://localhost:8080","account_domain":"localhost:8080","title":"GoToSocial Testrig Instance","description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","short_description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","email":"","version":"0.0.0-testrig","registrations":true,"approval_required":true,"invites_enabled":false,"configuration":{"statuses":{"max_characters":5000,"max_media_attachments":6,"characters_reserved_per_url":25},"media_attachments":{"supported_mime_types":["image/jpeg","image/gif","image/png"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":16777216},"polls":{"max_options":6,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":2,"status_count":16,"user_count":4},"thumbnail":"http://localhost:8080/assets/logo.png","contact_account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"max_toot_chars":5000}`, string(b)) suite.Equal(`{"uri":"http://localhost:8080","account_domain":"localhost:8080","title":"GoToSocial Testrig Instance","description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","short_description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","email":"","version":"0.0.0-testrig","registrations":true,"approval_required":true,"invites_enabled":false,"configuration":{"statuses":{"max_characters":5000,"max_media_attachments":6,"characters_reserved_per_url":25},"media_attachments":{"supported_mime_types":["image/jpeg","image/gif","image/png"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":16777216},"polls":{"max_options":6,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746},"accounts":{"allow_custom_css":true}},"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":2,"status_count":16,"user_count":4},"thumbnail":"http://localhost:8080/assets/logo.png","contact_account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"max_toot_chars":5000}`, string(b))
} }
func (suite *InstancePatchTestSuite) TestInstancePatch7() { func (suite *InstancePatchTestSuite) TestInstancePatch7() {

View File

@ -31,4 +31,5 @@
MultipartForm MIME = `multipart/form-data` MultipartForm MIME = `multipart/form-data`
TextXML MIME = `text/xml` TextXML MIME = `text/xml`
TextHTML MIME = `text/html` TextHTML MIME = `text/html`
TextCSS MIME = `text/css`
) )

View File

@ -90,6 +90,8 @@ type Account struct {
MuteExpiresAt string `json:"mute_expires_at,omitempty"` MuteExpiresAt string `json:"mute_expires_at,omitempty"`
// Extra profile information. Shown only if the requester owns the account being requested. // Extra profile information. Shown only if the requester owns the account being requested.
Source *Source `json:"source,omitempty"` Source *Source `json:"source,omitempty"`
// CustomCSS to include when rendering this account's profile or statuses.
CustomCSS string `json:"custom_css,omitempty"`
} }
// AccountCreateRequest models account creation parameters. // AccountCreateRequest models account creation parameters.
@ -151,6 +153,8 @@ type UpdateCredentialsRequest struct {
Source *UpdateSource `form:"source" json:"source" xml:"source"` Source *UpdateSource `form:"source" json:"source" xml:"source"`
// Profile metadata name and value // Profile metadata name and value
FieldsAttributes *[]UpdateField `form:"fields_attributes" json:"fields_attributes" xml:"fields_attributes"` FieldsAttributes *[]UpdateField `form:"fields_attributes" json:"fields_attributes" xml:"fields_attributes"`
// Custom CSS to be included when rendering this account's profile or statuses.
CustomCSS *string `form:"custom_css" json:"custom_css" xml:"custom_css"`
} }
// UpdateSource is to be used specifically in an UpdateCredentialsRequest. // UpdateSource is to be used specifically in an UpdateCredentialsRequest.

View File

@ -97,6 +97,8 @@ type InstanceConfiguration struct {
MediaAttachments *InstanceConfigurationMediaAttachments `json:"media_attachments"` MediaAttachments *InstanceConfigurationMediaAttachments `json:"media_attachments"`
// Instance configuration pertaining to poll limits. // Instance configuration pertaining to poll limits.
Polls *InstanceConfigurationPolls `json:"polls"` Polls *InstanceConfigurationPolls `json:"polls"`
// Instance configuration pertaining to accounts.
Accounts *InstanceConfigurationAccounts `json:"accounts"`
} }
// InstanceConfigurationStatuses models instance status config parameters. // InstanceConfigurationStatuses models instance status config parameters.
@ -175,6 +177,14 @@ type InstanceConfigurationPolls struct {
MaxExpiration int `json:"max_expiration"` MaxExpiration int `json:"max_expiration"`
} }
// InstanceConfigurationAccounts models instance account config parameters.
type InstanceConfigurationAccounts struct {
// Whether or not accounts on this instance are allowed to upload custom CSS for profiles and statuses.
//
// example: false
AllowCustomCSS bool `json:"allow_custom_css"`
}
// InstanceURLs models instance-relevant URLs for client application consumption. // InstanceURLs models instance-relevant URLs for client application consumption.
// //
// swagger:model instanceURLs // swagger:model instanceURLs

View File

@ -131,6 +131,7 @@ func copyAccount(account *gtsmodel.Account) *gtsmodel.Account {
Sensitive: copyBoolPtr(account.Sensitive), Sensitive: copyBoolPtr(account.Sensitive),
Language: account.Language, Language: account.Language,
StatusFormat: account.StatusFormat, StatusFormat: account.StatusFormat,
CustomCSS: account.CustomCSS,
URI: account.URI, URI: account.URI,
URL: account.URL, URL: account.URL,
LastWebfingeredAt: account.LastWebfingeredAt, LastWebfingeredAt: account.LastWebfingeredAt,

View File

@ -73,6 +73,7 @@ type Configuration struct {
AccountsRegistrationOpen bool `name:"accounts-registration-open" usage:"Allow anyone to submit an account signup request. If false, server will be invite-only."` AccountsRegistrationOpen bool `name:"accounts-registration-open" usage:"Allow anyone to submit an account signup request. If false, server will be invite-only."`
AccountsApprovalRequired bool `name:"accounts-approval-required" usage:"Do account signups require approval by an admin or moderator before user can log in? If false, new registrations will be automatically approved."` AccountsApprovalRequired bool `name:"accounts-approval-required" usage:"Do account signups require approval by an admin or moderator before user can log in? If false, new registrations will be automatically approved."`
AccountsReasonRequired bool `name:"accounts-reason-required" usage:"Do new account signups require a reason to be submitted on registration?"` AccountsReasonRequired bool `name:"accounts-reason-required" usage:"Do new account signups require a reason to be submitted on registration?"`
AccountsAllowCustomCSS bool `name:"accounts-allow-custom-css" usage:"Allow accounts to enable custom CSS for their profile pages and statuses."`
MediaImageMaxSize int `name:"media-image-max-size" usage:"Max size of accepted images in bytes"` MediaImageMaxSize int `name:"media-image-max-size" usage:"Max size of accepted images in bytes"`
MediaVideoMaxSize int `name:"media-video-max-size" usage:"Max size of accepted videos in bytes"` MediaVideoMaxSize int `name:"media-video-max-size" usage:"Max size of accepted videos in bytes"`

View File

@ -52,6 +52,7 @@
AccountsRegistrationOpen: true, AccountsRegistrationOpen: true,
AccountsApprovalRequired: true, AccountsApprovalRequired: true,
AccountsReasonRequired: true, AccountsReasonRequired: true,
AccountsAllowCustomCSS: false,
MediaImageMaxSize: 10485760, // 10mb MediaImageMaxSize: 10485760, // 10mb
MediaVideoMaxSize: 41943040, // 40mb MediaVideoMaxSize: 41943040, // 40mb

View File

@ -68,6 +68,7 @@ func AddServerFlags(cmd *cobra.Command) {
cmd.Flags().Bool(AccountsRegistrationOpenFlag(), cfg.AccountsRegistrationOpen, fieldtag("AccountsRegistrationOpen", "usage")) cmd.Flags().Bool(AccountsRegistrationOpenFlag(), cfg.AccountsRegistrationOpen, fieldtag("AccountsRegistrationOpen", "usage"))
cmd.Flags().Bool(AccountsApprovalRequiredFlag(), cfg.AccountsApprovalRequired, fieldtag("AccountsApprovalRequired", "usage")) cmd.Flags().Bool(AccountsApprovalRequiredFlag(), cfg.AccountsApprovalRequired, fieldtag("AccountsApprovalRequired", "usage"))
cmd.Flags().Bool(AccountsReasonRequiredFlag(), cfg.AccountsReasonRequired, fieldtag("AccountsReasonRequired", "usage")) cmd.Flags().Bool(AccountsReasonRequiredFlag(), cfg.AccountsReasonRequired, fieldtag("AccountsReasonRequired", "usage"))
cmd.Flags().Bool(AccountsAllowCustomCSSFlag(), cfg.AccountsAllowCustomCSS, fieldtag("AccountsAllowCustomCSS", "usage"))
// Media // Media
cmd.Flags().Int(MediaImageMaxSizeFlag(), cfg.MediaImageMaxSize, fieldtag("MediaImageMaxSize", "usage")) cmd.Flags().Int(MediaImageMaxSizeFlag(), cfg.MediaImageMaxSize, fieldtag("MediaImageMaxSize", "usage"))

View File

@ -668,6 +668,31 @@ func GetAccountsReasonRequired() bool { return global.GetAccountsReasonRequired(
// SetAccountsReasonRequired safely sets the value for global configuration 'AccountsReasonRequired' field // SetAccountsReasonRequired safely sets the value for global configuration 'AccountsReasonRequired' field
func SetAccountsReasonRequired(v bool) { global.SetAccountsReasonRequired(v) } func SetAccountsReasonRequired(v bool) { global.SetAccountsReasonRequired(v) }
// GetAccountsAllowCustomCSS safely fetches the Configuration value for state's 'AccountsAllowCustomCSS' field
func (st *ConfigState) GetAccountsAllowCustomCSS() (v bool) {
st.mutex.Lock()
v = st.config.AccountsAllowCustomCSS
st.mutex.Unlock()
return
}
// SetAccountsAllowCustomCSS safely sets the Configuration value for state's 'AccountsAllowCustomCSS' field
func (st *ConfigState) SetAccountsAllowCustomCSS(v bool) {
st.mutex.Lock()
defer st.mutex.Unlock()
st.config.AccountsAllowCustomCSS = v
st.reloadToViper()
}
// AccountsAllowCustomCSSFlag returns the flag name for the 'AccountsAllowCustomCSS' field
func AccountsAllowCustomCSSFlag() string { return "accounts-allow-custom-css" }
// GetAccountsAllowCustomCSS safely fetches the value for global configuration 'AccountsAllowCustomCSS' field
func GetAccountsAllowCustomCSS() bool { return global.GetAccountsAllowCustomCSS() }
// SetAccountsAllowCustomCSS safely sets the value for global configuration 'AccountsAllowCustomCSS' field
func SetAccountsAllowCustomCSS(v bool) { global.SetAccountsAllowCustomCSS(v) }
// GetMediaImageMaxSize safely fetches the Configuration value for state's 'MediaImageMaxSize' field // GetMediaImageMaxSize safely fetches the Configuration value for state's 'MediaImageMaxSize' field
func (st *ConfigState) GetMediaImageMaxSize() (v int) { func (st *ConfigState) GetMediaImageMaxSize() (v int) {
st.mutex.Lock() st.mutex.Lock()

View File

@ -45,6 +45,9 @@ type Account interface {
// UpdateAccount updates one account by ID. // UpdateAccount updates one account by ID.
UpdateAccount(ctx context.Context, account *gtsmodel.Account) (*gtsmodel.Account, Error) UpdateAccount(ctx context.Context, account *gtsmodel.Account) (*gtsmodel.Account, Error)
// GetAccountCustomCSSByUsername returns the custom css of an account on this instance with the given username.
GetAccountCustomCSSByUsername(ctx context.Context, username string) (string, Error)
// GetAccountFaves fetches faves/likes created by the target accountID. // GetAccountFaves fetches faves/likes created by the target accountID.
GetAccountFaves(ctx context.Context, accountID string) ([]*gtsmodel.StatusFave, Error) GetAccountFaves(ctx context.Context, accountID string) ([]*gtsmodel.StatusFave, Error)

View File

@ -231,6 +231,15 @@ func (a *accountDB) SetAccountHeaderOrAvatar(ctx context.Context, mediaAttachmen
return nil return nil
} }
func (a *accountDB) GetAccountCustomCSSByUsername(ctx context.Context, username string) (string, db.Error) {
account, err := a.GetAccountByUsernameDomain(ctx, username, "")
if err != nil {
return "", err
}
return account.CustomCSS, nil
}
func (a *accountDB) GetAccountFaves(ctx context.Context, accountID string) ([]*gtsmodel.StatusFave, db.Error) { func (a *accountDB) GetAccountFaves(ctx context.Context, accountID string) ([]*gtsmodel.StatusFave, db.Error) {
faves := new([]*gtsmodel.StatusFave) faves := new([]*gtsmodel.StatusFave)

View File

@ -0,0 +1,46 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package migrations
import (
"context"
"strings"
"github.com/uptrace/bun"
)
func init() {
up := func(ctx context.Context, db *bun.DB) error {
_, err := db.ExecContext(ctx, "ALTER TABLE ? ADD COLUMN ? TEXT", bun.Ident("accounts"), bun.Ident("custom_css"))
if err != nil && !(strings.Contains(err.Error(), "already exists") || strings.Contains(err.Error(), "duplicate column name") || strings.Contains(err.Error(), "SQLSTATE 42701")) {
return err
}
return nil
}
down := func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
return nil
})
}
if err := Migrations.Register(up, down); err != nil {
panic(err)
}
}

View File

@ -55,6 +55,7 @@ type Account struct {
Sensitive *bool `validate:"-" bun:",default:false"` // Set posts from this account to sensitive by default? Sensitive *bool `validate:"-" bun:",default:false"` // Set posts from this account to sensitive by default?
Language string `validate:"omitempty,bcp47_language_tag" bun:",nullzero,notnull,default:'en'"` // What language does this account post in? Language string `validate:"omitempty,bcp47_language_tag" bun:",nullzero,notnull,default:'en'"` // What language does this account post in?
StatusFormat string `validate:"required_without=Domain,omitempty,oneof=plain markdown" bun:",nullzero"` // What is the default format for statuses posted by this account (only for local accounts). StatusFormat string `validate:"required_without=Domain,omitempty,oneof=plain markdown" bun:",nullzero"` // What is the default format for statuses posted by this account (only for local accounts).
CustomCSS string `validate:"-" bun:",nullzero"` // Custom CSS that should be displayed for this Account's profile and statuses.
URI string `validate:"required,url" bun:",nullzero,notnull,unique"` // ActivityPub URI for this account. URI string `validate:"required,url" bun:",nullzero,notnull,unique"` // ActivityPub URI for this account.
URL string `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"` // Web URL for this account's profile URL string `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"` // Web URL for this account's profile
LastWebfingeredAt time.Time `validate:"required_with=Domain" bun:"type:timestamptz,nullzero"` // Last time this account was refreshed/located with webfinger. LastWebfingeredAt time.Time `validate:"required_with=Domain" bun:"type:timestamptz,nullzero"` // Last time this account was refreshed/located with webfinger.

View File

@ -42,6 +42,10 @@ func (p *processor) AccountGetLocalByUsername(ctx context.Context, authed *oauth
return p.accountProcessor.GetLocalByUsername(ctx, authed.Account, username) return p.accountProcessor.GetLocalByUsername(ctx, authed.Account, username)
} }
func (p *processor) AccountGetCustomCSSForUsername(ctx context.Context, username string) (string, gtserror.WithCode) {
return p.accountProcessor.GetCustomCSSForUsername(ctx, username)
}
func (p *processor) AccountUpdate(ctx context.Context, authed *oauth.Auth, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode) { func (p *processor) AccountUpdate(ctx context.Context, authed *oauth.Auth, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode) {
return p.accountProcessor.Update(ctx, authed.Account, form) return p.accountProcessor.Update(ctx, authed.Account, form)
} }

View File

@ -51,6 +51,8 @@ type Processor interface {
Get(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Account, gtserror.WithCode) Get(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Account, gtserror.WithCode)
// GetLocalByUsername processes the given request for account information targeting a local account by username. // GetLocalByUsername processes the given request for account information targeting a local account by username.
GetLocalByUsername(ctx context.Context, requestingAccount *gtsmodel.Account, username string) (*apimodel.Account, gtserror.WithCode) GetLocalByUsername(ctx context.Context, requestingAccount *gtsmodel.Account, username string) (*apimodel.Account, gtserror.WithCode)
// GetCustomCSSForUsername returns custom css for the given local username.
GetCustomCSSForUsername(ctx context.Context, username string) (string, gtserror.WithCode)
// Update processes the update of an account with the given form // Update processes the update of an account with the given form
Update(ctx context.Context, account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode) Update(ctx context.Context, account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode)
// StatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for // StatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for

View File

@ -55,6 +55,18 @@ func (p *processor) GetLocalByUsername(ctx context.Context, requestingAccount *g
return p.getAccountFor(ctx, requestingAccount, targetAccount) return p.getAccountFor(ctx, requestingAccount, targetAccount)
} }
func (p *processor) GetCustomCSSForUsername(ctx context.Context, username string) (string, gtserror.WithCode) {
customCSS, err := p.db.GetAccountCustomCSSByUsername(ctx, username)
if err != nil {
if err == db.ErrNoEntries {
return "", gtserror.NewErrorNotFound(errors.New("account not found"))
}
return "", gtserror.NewErrorInternalError(fmt.Errorf("db error: %s", err))
}
return customCSS, nil
}
func (p *processor) getAccountFor(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (*apimodel.Account, gtserror.WithCode) { func (p *processor) getAccountFor(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (*apimodel.Account, gtserror.WithCode) {
var blocked bool var blocked bool
var err error var err error

View File

@ -124,6 +124,14 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form
} }
} }
if form.CustomCSS != nil {
customCSS := *form.CustomCSS
if err := validate.CustomCSS(customCSS); err != nil {
return nil, gtserror.NewErrorBadRequest(err, err.Error())
}
account.CustomCSS = text.SanitizePlaintext(customCSS)
}
updatedAccount, err := p.db.UpdateAccount(ctx, account) updatedAccount, err := p.db.UpdateAccount(ctx, account)
if err != nil { if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("could not update account %s: %s", account.ID, err)) return nil, gtserror.NewErrorInternalError(fmt.Errorf("could not update account %s: %s", account.ID, err))

View File

@ -79,6 +79,7 @@ type Processor interface {
AccountGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Account, gtserror.WithCode) AccountGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Account, gtserror.WithCode)
// AccountGet processes the given request for account information. // AccountGet processes the given request for account information.
AccountGetLocalByUsername(ctx context.Context, authed *oauth.Auth, username string) (*apimodel.Account, gtserror.WithCode) AccountGetLocalByUsername(ctx context.Context, authed *oauth.Auth, username string) (*apimodel.Account, gtserror.WithCode)
AccountGetCustomCSSForUsername(ctx context.Context, username string) (string, gtserror.WithCode)
// AccountUpdate processes the update of an account with the given form // AccountUpdate processes the update of an account with the given form
AccountUpdate(ctx context.Context, authed *oauth.Auth, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode) AccountUpdate(ctx context.Context, authed *oauth.Auth, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode)
// AccountStatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for // AccountStatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for

View File

@ -94,6 +94,35 @@ func (suite *SanitizeTestSuite) TestSanitizeCaption6() {
suite.Equal("hello world", sanitized) suite.Equal("hello world", sanitized)
} }
func (suite *SanitizeTestSuite) TestSanitizeCustomCSS() {
customCSS := `.toot .username {
color: var(--link_fg);
line-height: 2rem;
margin-top: -0.5rem;
align-self: start;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}`
sanitized := text.SanitizePlaintext(customCSS)
suite.Equal(customCSS, sanitized) // should be the same as it was before
}
func (suite *SanitizeTestSuite) TestSanitizeNaughtyCustomCSS1() {
// try to break out of <style> into <head> and change the document title
customCSS := "</style><title>pee pee poo poo</title><style>"
sanitized := text.SanitizePlaintext(customCSS)
suite.Empty(sanitized)
}
func (suite *SanitizeTestSuite) TestSanitizeNaughtyCustomCSS2() {
// try to break out of <style> into <head> and change the document title
customCSS := "pee pee poo poo</style><title></title><style>"
sanitized := text.SanitizePlaintext(customCSS)
suite.Equal("pee pee poo poo", sanitized)
}
func TestSanitizeTestSuite(t *testing.T) { func TestSanitizeTestSuite(t *testing.T) {
suite.Run(t, new(SanitizeTestSuite)) suite.Run(t, new(SanitizeTestSuite))
} }

View File

@ -197,6 +197,7 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
Emojis: emojis, // TODO: implement this Emojis: emojis, // TODO: implement this
Fields: fields, Fields: fields,
Suspended: suspended, Suspended: suspended,
CustomCSS: a.CustomCSS,
} }
c.ensureAvatar(accountFrontend) c.ensureAvatar(accountFrontend)
@ -678,6 +679,9 @@ func (c *converter) InstanceToAPIInstance(ctx context.Context, i *gtsmodel.Insta
MinExpiration: instancePollsMinExpiration, // seconds MinExpiration: instancePollsMinExpiration, // seconds
MaxExpiration: instancePollsMaxExpiration, // seconds MaxExpiration: instancePollsMaxExpiration, // seconds
}, },
Accounts: &model.InstanceConfigurationAccounts{
AllowCustomCSS: config.GetAccountsAllowCustomCSS(),
},
} }
} }

View File

@ -25,6 +25,7 @@
"strings" "strings"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/regexes" "github.com/superseriousbusiness/gotosocial/internal/regexes"
pwv "github.com/wagslane/go-password-validator" pwv "github.com/wagslane/go-password-validator"
"golang.org/x/text/language" "golang.org/x/text/language"
@ -40,8 +41,7 @@
maximumDescriptionLength = 5000 maximumDescriptionLength = 5000
maximumSiteTermsLength = 5000 maximumSiteTermsLength = 5000
maximumUsernameLength = 64 maximumUsernameLength = 64
// maximumEmojiShortcodeLength = 30 maximumCustomCSSLength = 5000
// maximumHashtagLength = 30
) )
// NewPassword returns an error if the given password is not sufficiently strong, or nil if it's ok. // NewPassword returns an error if the given password is not sufficiently strong, or nil if it's ok.
@ -159,6 +159,17 @@ func StatusFormat(statusFormat string) error {
return fmt.Errorf("status format '%s' was not recognized, valid options are 'plain', 'markdown'", statusFormat) return fmt.Errorf("status format '%s' was not recognized, valid options are 'plain', 'markdown'", statusFormat)
} }
func CustomCSS(customCSS string) error {
if !config.GetAccountsAllowCustomCSS() {
return errors.New("accounts-allow-custom-css is not enabled for this instance")
}
if length := len(customCSS); length > maximumCustomCSSLength {
return fmt.Errorf("custom_css must be less than %d characters, but submitted custom_css was %d characters", maximumCustomCSSLength, length)
}
return nil
}
// EmojiShortcode just runs the given shortcode through the regular expression // EmojiShortcode just runs the given shortcode through the regular expression
// for emoji shortcodes, to figure out whether it's a valid shortcode, ie., 2-30 characters, // for emoji shortcodes, to figure out whether it's a valid shortcode, ie., 2-30 characters,
// lowercase a-z, numbers, and underscores. // lowercase a-z, numbers, and underscores.

60
internal/web/customcss.go Normal file
View File

@ -0,0 +1,60 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package web
import (
"errors"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/api"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
)
func (m *Module) customCSSGETHandler(c *gin.Context) {
if !config.GetAccountsAllowCustomCSS() {
err := errors.New("accounts-allow-custom-css is not enabled on this instance")
api.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet)
return
}
if _, err := api.NegotiateAccept(c, api.TextCSS); err != nil {
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
return
}
// usernames on our instance will always be lowercase
username := strings.ToLower(c.Param(usernameKey))
if username == "" {
err := errors.New("no account username specified")
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
return
}
customCSS, errWithCode := m.processor.AccountGetCustomCSSForUsername(c.Request.Context(), username)
if errWithCode != nil {
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
return
}
c.Header("Cache-Control", "no-cache")
c.Data(http.StatusOK, "text/css; charset=utf-8", []byte(customCSS))
}

View File

@ -99,6 +99,15 @@ func (m *Module) profileGETHandler(c *gin.Context) {
return return
} }
stylesheets := []string{
"/assets/Fork-Awesome/css/fork-awesome.min.css",
"/assets/dist/status.css",
"/assets/dist/profile.css",
}
if config.GetAccountsAllowCustomCSS() {
stylesheets = append(stylesheets, "/@"+account.Username+"/custom.css")
}
c.HTML(http.StatusOK, "profile.tmpl", gin.H{ c.HTML(http.StatusOK, "profile.tmpl", gin.H{
"instance": instance, "instance": instance,
"account": account, "account": account,
@ -106,11 +115,7 @@ func (m *Module) profileGETHandler(c *gin.Context) {
"statuses": statusResp.Items, "statuses": statusResp.Items,
"statuses_next": statusResp.NextLink, "statuses_next": statusResp.NextLink,
"show_back_to_top": showBackToTop, "show_back_to_top": showBackToTop,
"stylesheets": []string{ "stylesheets": stylesheets,
"/assets/Fork-Awesome/css/fork-awesome.min.css",
"/assets/dist/status.css",
"/assets/dist/profile.css",
},
"javascript": []string{ "javascript": []string{
"/assets/dist/frontend.js", "/assets/dist/frontend.js",
}, },

View File

@ -104,15 +104,20 @@ func (m *Module) threadGETHandler(c *gin.Context) {
return return
} }
stylesheets := []string{
"/assets/Fork-Awesome/css/fork-awesome.min.css",
"/assets/dist/status.css",
}
if config.GetAccountsAllowCustomCSS() {
stylesheets = append(stylesheets, "/@"+username+"/custom.css")
}
c.HTML(http.StatusOK, "thread.tmpl", gin.H{ c.HTML(http.StatusOK, "thread.tmpl", gin.H{
"instance": instance, "instance": instance,
"status": status, "status": status,
"context": context, "context": context,
"ogMeta": ogBase(instance).withStatus(status), "ogMeta": ogBase(instance).withStatus(status),
"stylesheets": []string{ "stylesheets": stylesheets,
"/assets/Fork-Awesome/css/fork-awesome.min.css",
"/assets/dist/status.css",
},
"javascript": []string{ "javascript": []string{
"/assets/dist/frontend.js", "/assets/dist/frontend.js",
}, },

View File

@ -35,6 +35,7 @@
const ( const (
confirmEmailPath = "/" + uris.ConfirmEmailPath confirmEmailPath = "/" + uris.ConfirmEmailPath
profilePath = "/@:" + usernameKey profilePath = "/@:" + usernameKey
customCSSPath = profilePath + "/custom.css"
statusPath = profilePath + "/statuses/:" + statusIDKey statusPath = profilePath + "/statuses/:" + statusIDKey
adminPanelPath = "/admin" adminPanelPath = "/admin"
userPanelpath = "/user" userPanelpath = "/user"
@ -91,6 +92,9 @@ func (m *Module) Route(s router.Router) error {
// serve profile pages at /@username // serve profile pages at /@username
s.AttachHandler(http.MethodGet, profilePath, m.profileGETHandler) s.AttachHandler(http.MethodGet, profilePath, m.profileGETHandler)
// serve custom css at /@username/custom.css
s.AttachHandler(http.MethodGet, customCSSPath, m.customCSSGETHandler)
// serve statuses // serve statuses
s.AttachHandler(http.MethodGet, statusPath, m.threadGETHandler) s.AttachHandler(http.MethodGet, statusPath, m.threadGETHandler)

View File

@ -16,6 +16,7 @@ nav:
- "User Guide": - "User Guide":
- "user_guide/posts.md" - "user_guide/posts.md"
- "user_guide/user_panel.md" - "user_guide/user_panel.md"
- "user_guide/custom_css.md"
- "user_guide/password_management.md" - "user_guide/password_management.md"
- "Installation Guide": - "Installation Guide":
- "installation_guide/index.md" - "installation_guide/index.md"

View File

@ -5,7 +5,7 @@ set -e
echo "STARTING CLI TESTS" echo "STARTING CLI TESTS"
echo "TEST_1 Make sure defaults are set correctly." echo "TEST_1 Make sure defaults are set correctly."
TEST_1_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","email":"","host":"","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_1_EXPECTED='{"account-domain":"","accounts-allow-custom-css":false,"accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","email":"","host":"","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}'
TEST_1="$(go run ./cmd/gotosocial/... debug config)" TEST_1="$(go run ./cmd/gotosocial/... debug config)"
if [ "${TEST_1}" != "${TEST_1_EXPECTED}" ]; then if [ "${TEST_1}" != "${TEST_1_EXPECTED}" ]; then
echo "TEST_1 not equal TEST_1_EXPECTED" echo "TEST_1 not equal TEST_1_EXPECTED"
@ -15,7 +15,7 @@ else
fi fi
echo "TEST_2 Override db-address from default using cli flag." echo "TEST_2 Override db-address from default using cli flag."
TEST_2_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"some.db.address","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","email":"","host":"","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_2_EXPECTED='{"account-domain":"","accounts-allow-custom-css":false,"accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"some.db.address","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","email":"","host":"","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}'
TEST_2="$(go run ./cmd/gotosocial/... --db-address some.db.address debug config)" TEST_2="$(go run ./cmd/gotosocial/... --db-address some.db.address debug config)"
if [ "${TEST_2}" != "${TEST_2_EXPECTED}" ]; then if [ "${TEST_2}" != "${TEST_2_EXPECTED}" ]; then
echo "TEST_2 not equal TEST_2_EXPECTED" echo "TEST_2 not equal TEST_2_EXPECTED"
@ -25,7 +25,7 @@ else
fi fi
echo "TEST_3 Override db-address from default using env var." echo "TEST_3 Override db-address from default using env var."
TEST_3_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"some.db.address","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","email":"","host":"","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_3_EXPECTED='{"account-domain":"","accounts-allow-custom-css":false,"accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"some.db.address","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","email":"","host":"","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}'
TEST_3="$(GTS_DB_ADDRESS=some.db.address go run ./cmd/gotosocial/... debug config)" TEST_3="$(GTS_DB_ADDRESS=some.db.address go run ./cmd/gotosocial/... debug config)"
if [ "${TEST_3}" != "${TEST_3_EXPECTED}" ]; then if [ "${TEST_3}" != "${TEST_3_EXPECTED}" ]; then
echo "TEST_3 not equal TEST_3_EXPECTED" echo "TEST_3 not equal TEST_3_EXPECTED"
@ -35,7 +35,7 @@ else
fi fi
echo "TEST_4 Override db-address from default using both env var and cli flag. The cli flag should take priority." echo "TEST_4 Override db-address from default using both env var and cli flag. The cli flag should take priority."
TEST_4_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"some.other.db.address","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","email":"","host":"","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_4_EXPECTED='{"account-domain":"","accounts-allow-custom-css":false,"accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"","db-address":"some.other.db.address","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","email":"","host":"","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}'
TEST_4="$(GTS_DB_ADDRESS=some.db.address go run ./cmd/gotosocial/... --db-address some.other.db.address debug config)" TEST_4="$(GTS_DB_ADDRESS=some.db.address go run ./cmd/gotosocial/... --db-address some.other.db.address debug config)"
if [ "${TEST_4}" != "${TEST_4_EXPECTED}" ]; then if [ "${TEST_4}" != "${TEST_4_EXPECTED}" ]; then
echo "TEST_4 not equal TEST_4_EXPECTED" echo "TEST_4 not equal TEST_4_EXPECTED"
@ -45,7 +45,7 @@ else
fi fi
echo "TEST_5 Test loading a config file by passing an env var." echo "TEST_5 Test loading a config file by passing an env var."
TEST_5_EXPECTED='{"account-domain":"example.org","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","email":"","host":"gts.example.org","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_5_EXPECTED='{"account-domain":"example.org","accounts-allow-custom-css":false,"accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","email":"","host":"gts.example.org","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}'
TEST_5="$(GTS_CONFIG_PATH=./test/test.yaml go run ./cmd/gotosocial/... debug config)" TEST_5="$(GTS_CONFIG_PATH=./test/test.yaml go run ./cmd/gotosocial/... debug config)"
if [ "${TEST_5}" != "${TEST_5_EXPECTED}" ]; then if [ "${TEST_5}" != "${TEST_5_EXPECTED}" ]; then
echo "TEST_5 not equal TEST_5_EXPECTED" echo "TEST_5 not equal TEST_5_EXPECTED"
@ -55,7 +55,7 @@ else
fi fi
echo "TEST_6 Test loading a config file by passing cli flag." echo "TEST_6 Test loading a config file by passing cli flag."
TEST_6_EXPECTED='{"account-domain":"example.org","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","email":"","host":"gts.example.org","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_6_EXPECTED='{"account-domain":"example.org","accounts-allow-custom-css":false,"accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","email":"","host":"gts.example.org","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}'
TEST_6="$(go run ./cmd/gotosocial/... --config-path ./test/test.yaml debug config)" TEST_6="$(go run ./cmd/gotosocial/... --config-path ./test/test.yaml debug config)"
if [ "${TEST_6}" != "${TEST_6_EXPECTED}" ]; then if [ "${TEST_6}" != "${TEST_6_EXPECTED}" ]; then
echo "TEST_6 not equal TEST_6_EXPECTED" echo "TEST_6 not equal TEST_6_EXPECTED"
@ -65,7 +65,7 @@ else
fi fi
echo "TEST_7 Test loading a config file and overriding one of the variables with a cli flag." echo "TEST_7 Test loading a config file and overriding one of the variables with a cli flag."
TEST_7_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","email":"","host":"gts.example.org","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_7_EXPECTED='{"account-domain":"","accounts-allow-custom-css":false,"accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","email":"","host":"gts.example.org","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}'
TEST_7="$(go run ./cmd/gotosocial/... --config-path ./test/test.yaml --account-domain '' debug config)" TEST_7="$(go run ./cmd/gotosocial/... --config-path ./test/test.yaml --account-domain '' debug config)"
if [ "${TEST_7}" != "${TEST_7_EXPECTED}" ]; then if [ "${TEST_7}" != "${TEST_7_EXPECTED}" ]; then
echo "TEST_7 not equal TEST_7_EXPECTED" echo "TEST_7 not equal TEST_7_EXPECTED"
@ -75,7 +75,7 @@ else
fi fi
echo "TEST_8 Test loading a config file and overriding one of the variables with an env var." echo "TEST_8 Test loading a config file and overriding one of the variables with an env var."
TEST_8_EXPECTED='{"account-domain":"peepee","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","email":"","host":"gts.example.org","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_8_EXPECTED='{"account-domain":"peepee","accounts-allow-custom-css":false,"accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","email":"","host":"gts.example.org","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}'
TEST_8="$(GTS_ACCOUNT_DOMAIN='peepee' go run ./cmd/gotosocial/... --config-path ./test/test.yaml debug config)" TEST_8="$(GTS_ACCOUNT_DOMAIN='peepee' go run ./cmd/gotosocial/... --config-path ./test/test.yaml debug config)"
if [ "${TEST_8}" != "${TEST_8_EXPECTED}" ]; then if [ "${TEST_8}" != "${TEST_8_EXPECTED}" ]; then
echo "TEST_8 not equal TEST_8_EXPECTED" echo "TEST_8 not equal TEST_8_EXPECTED"
@ -85,7 +85,7 @@ else
fi fi
echo "TEST_9 Test loading a config file and overriding one of the variables with both an env var and a cli flag. The cli flag should have priority." echo "TEST_9 Test loading a config file and overriding one of the variables with both an env var and a cli flag. The cli flag should have priority."
TEST_9_EXPECTED='{"account-domain":"","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","email":"","host":"gts.example.org","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_9_EXPECTED='{"account-domain":"","accounts-allow-custom-css":false,"accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.yaml","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","email":"","host":"gts.example.org","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}'
TEST_9="$(GTS_ACCOUNT_DOMAIN='peepee' go run ./cmd/gotosocial/... --config-path ./test/test.yaml --account-domain '' debug config)" TEST_9="$(GTS_ACCOUNT_DOMAIN='peepee' go run ./cmd/gotosocial/... --config-path ./test/test.yaml --account-domain '' debug config)"
if [ "${TEST_9}" != "${TEST_9_EXPECTED}" ]; then if [ "${TEST_9}" != "${TEST_9_EXPECTED}" ]; then
echo "TEST_9 not equal TEST_9_EXPECTED" echo "TEST_9 not equal TEST_9_EXPECTED"
@ -95,7 +95,7 @@ else
fi fi
echo "TEST_10 Test loading a config file from json." echo "TEST_10 Test loading a config file from json."
TEST_10_EXPECTED='{"account-domain":"example.org","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.json","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","email":"","host":"gts.example.org","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_10_EXPECTED='{"account-domain":"example.org","accounts-allow-custom-css":false,"accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test.json","db-address":"127.0.0.1","db-database":"postgres","db-password":"postgres","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"postgres","email":"","host":"gts.example.org","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":false,"log-level":"info","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","email","profile","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"someone@example.org","smtp-host":"verycoolemailhost.mail","smtp-password":"smtp-password","smtp-port":8888,"smtp-username":"smtp-username","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}'
TEST_10="$(go run ./cmd/gotosocial/... --config-path ./test/test.json debug config)" TEST_10="$(go run ./cmd/gotosocial/... --config-path ./test/test.json debug config)"
if [ "${TEST_10}" != "${TEST_10_EXPECTED}" ]; then if [ "${TEST_10}" != "${TEST_10_EXPECTED}" ]; then
echo "TEST_10 not equal TEST_10_EXPECTED" echo "TEST_10 not equal TEST_10_EXPECTED"
@ -105,7 +105,7 @@ else
fi fi
echo "TEST_11 Test loading a partial config file. Default values should be used apart from those set in the config file." echo "TEST_11 Test loading a partial config file. Default values should be used apart from those set in the config file."
TEST_11_EXPECTED='{"account-domain":"peepee.poopoo","accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test2.yaml","db-address":"","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","email":"","host":"","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"trace","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}' TEST_11_EXPECTED='{"account-domain":"peepee.poopoo","accounts-allow-custom-css":false,"accounts-approval-required":true,"accounts-reason-required":true,"accounts-registration-open":true,"advanced-cookies-samesite":"lax","application-name":"gotosocial","bind-address":"0.0.0.0","config-path":"./test/test2.yaml","db-address":"","db-database":"gotosocial","db-password":"","db-port":5432,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"postgres","db-user":"","email":"","host":"","instance-expose-peers":false,"instance-expose-suspended":false,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":false,"letsencrypt-port":80,"log-db-queries":false,"log-level":"trace","media-description-max-chars":500,"media-description-min-chars":0,"media-emoji-local-max-size":51200,"media-emoji-remote-max-size":102400,"media-image-max-size":10485760,"media-remote-cache-days":30,"media-video-max-size":41943040,"oidc-client-id":"","oidc-client-secret":"","oidc-enabled":false,"oidc-idp-name":"","oidc-issuer":"","oidc-scopes":["openid","profile","email","groups"],"oidc-skip-verification":false,"password":"","path":"","port":8080,"protocol":"https","smtp-from":"GoToSocial","smtp-host":"","smtp-password":"","smtp-port":0,"smtp-username":"","software-version":"","statuses-cw-max-chars":100,"statuses-max-chars":5000,"statuses-media-max-files":6,"statuses-poll-max-options":6,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/gotosocial/storage","storage-s3-access-key":"","storage-s3-bucket":"","storage-s3-endpoint":"","storage-s3-secret-key":"","storage-s3-use-ssl":true,"syslog-address":"localhost:514","syslog-enabled":false,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32"],"username":"","web-asset-base-dir":"./web/assets/","web-template-base-dir":"./web/template/"}'
TEST_11="$(go run ./cmd/gotosocial/... --config-path ./test/test2.yaml debug config)" TEST_11="$(go run ./cmd/gotosocial/... --config-path ./test/test2.yaml debug config)"
if [ "${TEST_11}" != "${TEST_11_EXPECTED}" ]; then if [ "${TEST_11}" != "${TEST_11_EXPECTED}" ]; then
echo "TEST_11 not equal TEST_11_EXPECTED" echo "TEST_11 not equal TEST_11_EXPECTED"

View File

@ -2,7 +2,7 @@
set -eu set -eu
EXPECTED='{"account-domain":"peepee","accounts-approval-required":false,"accounts-reason-required":false,"accounts-registration-open":true,"advanced-cookies-samesite":"strict","application-name":"gts","bind-address":"127.0.0.1","config-path":"./test/test.yaml","db-address":":memory:","db-database":"gotosocial_prod","db-password":"hunter2","db-port":6969,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"sqlite","db-user":"sex-haver","email":"","host":"example.com","instance-expose-peers":true,"instance-expose-suspended":true,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":true,"log-level":"info","media-description-max-chars":5000,"media-description-min-chars":69,"media-emoji-local-max-size":420,"media-emoji-remote-max-size":420,"media-image-max-size":420,"media-remote-cache-days":30,"media-video-max-size":420,"oidc-client-id":"1234","oidc-client-secret":"shhhh its a secret","oidc-enabled":true,"oidc-idp-name":"sex-haver","oidc-issuer":"whoknows","oidc-scopes":["read","write"],"oidc-skip-verification":true,"password":"","path":"","port":6969,"protocol":"http","smtp-from":"queen@terfisland.org","smtp-host":"example.com","smtp-password":"hunter2","smtp-port":4269,"smtp-username":"sex-haver","software-version":"","statuses-cw-max-chars":420,"statuses-max-chars":69,"statuses-media-max-files":1,"statuses-poll-max-options":1,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/root/store","storage-s3-access-key":"minio","storage-s3-bucket":"gts","storage-s3-endpoint":"localhost:9000","storage-s3-secret-key":"miniostorage","storage-s3-use-ssl":false,"syslog-address":"127.0.0.1:6969","syslog-enabled":true,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"/root","web-template-base-dir":"/root"}' EXPECTED='{"account-domain":"peepee","accounts-allow-custom-css":true,"accounts-approval-required":false,"accounts-reason-required":false,"accounts-registration-open":true,"advanced-cookies-samesite":"strict","application-name":"gts","bind-address":"127.0.0.1","config-path":"./test/test.yaml","db-address":":memory:","db-database":"gotosocial_prod","db-password":"hunter2","db-port":6969,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"sqlite","db-user":"sex-haver","email":"","host":"example.com","instance-expose-peers":true,"instance-expose-suspended":true,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":true,"log-level":"info","media-description-max-chars":5000,"media-description-min-chars":69,"media-emoji-local-max-size":420,"media-emoji-remote-max-size":420,"media-image-max-size":420,"media-remote-cache-days":30,"media-video-max-size":420,"oidc-client-id":"1234","oidc-client-secret":"shhhh its a secret","oidc-enabled":true,"oidc-idp-name":"sex-haver","oidc-issuer":"whoknows","oidc-scopes":["read","write"],"oidc-skip-verification":true,"password":"","path":"","port":6969,"protocol":"http","smtp-from":"queen@terfisland.org","smtp-host":"example.com","smtp-password":"hunter2","smtp-port":4269,"smtp-username":"sex-haver","software-version":"","statuses-cw-max-chars":420,"statuses-max-chars":69,"statuses-media-max-files":1,"statuses-poll-max-options":1,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/root/store","storage-s3-access-key":"minio","storage-s3-bucket":"gts","storage-s3-endpoint":"localhost:9000","storage-s3-secret-key":"miniostorage","storage-s3-use-ssl":false,"syslog-address":"127.0.0.1:6969","syslog-enabled":true,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","0.0.0.0/0"],"username":"","web-asset-base-dir":"/root","web-template-base-dir":"/root"}'
# Set all the environment variables to # Set all the environment variables to
# ensure that these are parsed without panic # ensure that these are parsed without panic
@ -27,6 +27,7 @@ GTS_WEB_TEMPLATE_BASE_DIR='/root' \
GTS_WEB_ASSET_BASE_DIR='/root' \ GTS_WEB_ASSET_BASE_DIR='/root' \
GTS_INSTANCE_EXPOSE_PEERS=true \ GTS_INSTANCE_EXPOSE_PEERS=true \
GTS_INSTANCE_EXPOSE_SUSPENDED=true \ GTS_INSTANCE_EXPOSE_SUSPENDED=true \
GTS_ACCOUNTS_ALLOW_CUSTOM_CSS=true \
GTS_ACCOUNTS_REGISTRATION_OPEN=true \ GTS_ACCOUNTS_REGISTRATION_OPEN=true \
GTS_ACCOUNTS_APPROVAL_REQUIRED=false \ GTS_ACCOUNTS_APPROVAL_REQUIRED=false \
GTS_ACCOUNTS_REASON_REQUIRED=false \ GTS_ACCOUNTS_REASON_REQUIRED=false \

View File

@ -58,6 +58,7 @@ func InitTestConfig() {
AccountsRegistrationOpen: true, AccountsRegistrationOpen: true,
AccountsApprovalRequired: true, AccountsApprovalRequired: true,
AccountsReasonRequired: true, AccountsReasonRequired: true,
AccountsAllowCustomCSS: true,
MediaImageMaxSize: 10485760, // 10mb MediaImageMaxSize: 10485760, // 10mb
MediaVideoMaxSize: 41943040, // 40mb MediaVideoMaxSize: 41943040, // 40mb

View File

@ -61,3 +61,7 @@ input, select, textarea {
rgb(70, 79, 88) 20px rgb(70, 79, 88) 20px
) !important; ) !important;
} }
.mono {
font-family: monospace;
}

View File

@ -23,7 +23,7 @@ const Promise = require("bluebird");
const Submit = require("../../lib/submit"); const Submit = require("../../lib/submit");
module.exports = function Basic({oauth, account}) { module.exports = function Basic({oauth, account, allowCustomCSS}) {
const [errorMsg, setError] = React.useState(""); const [errorMsg, setError] = React.useState("");
const [statusMsg, setStatus] = React.useState(""); const [statusMsg, setStatus] = React.useState("");
@ -36,6 +36,7 @@ module.exports = function Basic({oauth, account}) {
const [displayName, setDisplayName] = React.useState(""); const [displayName, setDisplayName] = React.useState("");
const [bio, setBio] = React.useState(""); const [bio, setBio] = React.useState("");
const [locked, setLocked] = React.useState(false); const [locked, setLocked] = React.useState(false);
const [customCSS, setCustomCSS] = React.useState("");
React.useEffect(() => { React.useEffect(() => {
setHeaderSrc(account.header); setHeaderSrc(account.header);
@ -44,7 +45,8 @@ module.exports = function Basic({oauth, account}) {
setDisplayName(account.display_name); setDisplayName(account.display_name);
setBio(account.source ? account.source.note : ""); setBio(account.source ? account.source.note : "");
setLocked(account.locked); setLocked(account.locked);
}, [account, setHeaderSrc, setAvatarSrc, setDisplayName, setBio, setLocked]); setCustomCSS((allowCustomCSS && account.custom_css) ? account.custom_css : "");
}, [account, setHeaderSrc, setAvatarSrc, setDisplayName, setBio, setLocked, setCustomCSS]);
const headerOnChange = (e) => { const headerOnChange = (e) => {
setHeaderFile(e.target.files[0]); setHeaderFile(e.target.files[0]);
@ -76,6 +78,10 @@ module.exports = function Basic({oauth, account}) {
formDataInfo.set("note", bio); formDataInfo.set("note", bio);
formDataInfo.set("locked", locked); formDataInfo.set("locked", locked);
if (allowCustomCSS) {
formDataInfo.set("custom_css", customCSS);
}
return oauth.apiRequest("/api/v1/accounts/update_credentials", "PATCH", formDataInfo, "form"); return oauth.apiRequest("/api/v1/accounts/update_credentials", "PATCH", formDataInfo, "form");
}).then((json) => { }).then((json) => {
setStatus("Saved!"); setStatus("Saved!");
@ -86,6 +92,7 @@ module.exports = function Basic({oauth, account}) {
setDisplayName(json.display_name); setDisplayName(json.display_name);
setBio(json.source.note); setBio(json.source.note);
setLocked(json.locked); setLocked(json.locked);
setCustomCSS(allowCustomCSS && json.custom_css ? json.custom_css : "");
}).catch((e) => { }).catch((e) => {
setError(e.message); setError(e.message);
setStatus(""); setStatus("");
@ -126,6 +133,13 @@ module.exports = function Basic({oauth, account}) {
<label htmlFor="bio">Bio</label> <label htmlFor="bio">Bio</label>
<textarea id="bio" value={bio} onChange={(e) => setBio(e.target.value)} placeholder="Just trying out GoToSocial, my pronouns are they/them and I like sloths."/> <textarea id="bio" value={bio} onChange={(e) => setBio(e.target.value)} placeholder="Just trying out GoToSocial, my pronouns are they/them and I like sloths."/>
</div> </div>
{ !allowCustomCSS ? null :
<div className="labelinput">
<label htmlFor="customcss">Custom CSS</label>
<textarea className="mono" id="customcss" value={customCSS} onChange={(e) => setCustomCSS(e.target.value)}/>
<a href="https://docs.gotosocial.org/en/latest/user_guide/custom_css" target="_blank" className="moreinfolink" rel="noreferrer">Learn more about custom CSS (opens in a new tab)</a>
</div>
}
<div className="labelcheckbox"> <div className="labelcheckbox">
<label htmlFor="locked">Manually approve follow requests</label> <label htmlFor="locked">Manually approve follow requests</label>
<input id="locked" type="checkbox" checked={locked} onChange={(e) => setLocked(e.target.checked)}/> <input id="locked" type="checkbox" checked={locked} onChange={(e) => setLocked(e.target.checked)}/>

View File

@ -33,10 +33,19 @@ require("./style.css");
function UserPanel({oauth}) { function UserPanel({oauth}) {
const [account, setAccount] = React.useState({}); const [account, setAccount] = React.useState({});
const [allowCustomCSS, setAllowCustomCSS] = React.useState(false);
const [errorMsg, setError] = React.useState(""); const [errorMsg, setError] = React.useState("");
const [statusMsg, setStatus] = React.useState("Fetching user info"); const [statusMsg, setStatus] = React.useState("Fetching user info");
React.useEffect(() => { React.useEffect(() => {
}, [oauth, setAllowCustomCSS, setError, setStatus]);
React.useEffect(() => {
Promise.try(() => {
return oauth.apiRequest("/api/v1/instance", "GET");
}).then((json) => {
setAllowCustomCSS(json.configuration.accounts.allow_custom_css);
Promise.try(() => { Promise.try(() => {
return oauth.apiRequest("/api/v1/accounts/verify_credentials", "GET"); return oauth.apiRequest("/api/v1/accounts/verify_credentials", "GET");
}).then((json) => { }).then((json) => {
@ -45,14 +54,19 @@ function UserPanel({oauth}) {
setError(e.message); setError(e.message);
setStatus(""); setStatus("");
}); });
}, [oauth, setAccount, setError, setStatus]); }).catch((e) => {
setError(e.message);
setStatus("");
});
}, [oauth, setAllowCustomCSS, setAccount, setError, setStatus]);
return ( return (
<React.Fragment> <React.Fragment>
<div> <div>
<button className="logout" onClick={oauth.logout}>Log out of settings panel</button> <button className="logout" onClick={oauth.logout}>Log out of settings panel</button>
</div> </div>
<Basic oauth={oauth} account={account}/> <Basic oauth={oauth} account={account} allowCustomCSS={allowCustomCSS}/>
<Posts oauth={oauth} account={account}/> <Posts oauth={oauth} account={account}/>
<Security oauth={oauth}/> <Security oauth={oauth}/>
</React.Fragment> </React.Fragment>