10 Commits

Author SHA1 Message Date
Matt Baer
3576ab15d1 Create issue templates 2018-11-12 20:12:31 -05:00
Matt Baer
78953f27f0 Fix badge 2018-11-12 13:40:38 -05:00
Matt Baer
002d0e6309 Bump version to 0.2 2018-11-11 18:37:30 -05:00
Matt Baer
b8ce944b5c Add IRC badge in README 2018-11-11 18:37:13 -05:00
Matt Baer
7bc873580c Move key generation to app from keys.sh
This eliminates an external dependency needed for install, and ensures
the app can run on Windows.
2018-11-11 17:52:24 -05:00
Matt Baer
96c197453d Fix key loading on Windows + move paths into vars
This uses filepath.Join() to make sure they always load correctly
2018-11-11 17:25:34 -05:00
Matt Baer
561568343a Use avatar as blog link social media image 2018-11-11 15:34:26 -05:00
Matt Baer
c996ae1cad Add To and CC on Create activities
Part of #8
2018-11-11 13:11:01 -05:00
Matt Baer
393f6d6834 Add ID on Accept activities
Part of #8
2018-11-11 13:10:39 -05:00
Matt Baer
bbed72ff6b Remove unneeded followers column from remoteusers
To migrate:

  ALTER TABLE `remoteusers` DROP `followers`;
2018-11-11 12:43:24 -05:00
14 changed files with 146 additions and 44 deletions

24
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,24 @@
---
name: Bug report
about: Let us know what went wrong.
---
### Describe the bug
Explain what the bug is, in as much detail as possible...
### Steps to reproduce (if necessary)
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. ...
### Expected behavior
What should've happened?
### Application configuration
- **Single mode or Multi-user mode?**
- **Open registration?** [yes/no]
- **Federation enabled?** [yes/no]
**Version or last commit**:

View File

@@ -0,0 +1,9 @@
---
name: Feature request
about: Suggest an idea for this project
---
# PLEASE DON'T SUBMIT FEATURE REQUESTS HERE #
Instead, post them to our forums: https://discuss.write.as/c/feedback/feature-requests

View File

@@ -20,8 +20,8 @@ run:
deps :
$(GOGET) -v ./...
install :
./keys.sh
install : build
cmd/writefreely/$(BINARY_NAME) --gen-keys
cd less/; $(MAKE) install $(MFLAGS)
ui : force_look

View File

@@ -1,6 +1,6 @@
 
<p align="center">
<a href="https://writefreely.org"><img src="https://writefreely.org/writefreely.svg" width="350px" alt="Write Freely" /></a>
<a href="https://writefreely.org"><img src="https://writefreely.org/img/writefreely.svg" width="350px" alt="Write Freely" /></a>
</p>
<hr />
<p align="center">
@@ -13,6 +13,9 @@
<a href="https://travis-ci.org/writeas/writefreely">
<img src="https://travis-ci.org/writeas/writefreely.svg" alt="Build status" />
</a>
<a href="http://webchat.freenode.net/?channels=writefreely">
<img alt="#writefreely on freenode" src="https://img.shields.io/badge/freenode-%23writefreely-blue.svg" />
</a>
</p>
&nbsp;
@@ -52,8 +55,8 @@ mysql -u YOURUSERNAME -p writefreely < schema.sql
# 3) Configure your blog
./writefreely --config
# 4) Generate data encryption keys (especially for production)
./keys.sh
# 4) Generate data encryption keys
./writefreely --gen-keys
# 5) Run
./writefreely
@@ -79,9 +82,7 @@ Ready to hack on your site? Here's a quick overview.
go get github.com/writeas/writefreely/cmd/writefreely
```
Create your database, import the schema, and configure your site [as shown above](#quick-start).
Now generate the CSS:
Create your database, import the schema, and configure your site [as shown above](#quick-start). Then generate the remaining files you'll need:
```bash
make install # Generates encryption keys; installs LESS compiler

View File

@@ -255,6 +255,16 @@ func handleFetchCollectionInbox(app *app, w http.ResponseWriter, r *http.Request
b, _ := json.Marshal(m)
log.Info("Follow: %s", b)
_, followID := f.GetId()
if followID == nil {
log.Error("Didn't resolve follow ID")
} else {
acceptID, err := url.Parse(followID.String() + "-accept")
if err != nil {
log.Error("Couldn't parse generated Accept URL '%s': %v", followID.String()+"#accept", err)
}
a.SetId(acceptID)
}
a.AppendObject(f.Raw())
_, to = f.GetActor(0)
obj := f.Raw().GetObjectIRI(0)
@@ -580,6 +590,8 @@ func federatePost(app *app, p *PublicPost, collID int64, isUpdate bool) error {
activity = activitystreams.NewUpdateActivity(na)
} else {
activity = activitystreams.NewCreateActivity(na)
activity.To = na.To
activity.CC = na.CC
}
err = makeActivityPost(actor, si, activity)
if err != nil {

21
app.go
View File

@@ -29,7 +29,8 @@ const (
serverSoftware = "WriteFreely"
softwareURL = "https://writefreely.org"
softwareVer = "0.1"
softwareVer = "0.2"
)
var (
@@ -124,6 +125,7 @@ func Serve() {
debugPtr := flag.Bool("debug", false, "Enables debug logging.")
createConfig := flag.Bool("create-config", false, "Creates a basic configuration and exits")
doConfig := flag.Bool("config", false, "Run the configuration process")
genKeys := flag.Bool("gen-keys", false, "Generate encryption and authentication keys")
flag.Parse()
debugging = *debugPtr
@@ -167,6 +169,23 @@ func Serve() {
log.Info("Done!")
}
os.Exit(0)
} else if *genKeys {
errStatus := 0
err := generateKey(emailKeyPath)
if err != nil {
errStatus = 1
}
err = generateKey(cookieAuthKeyPath)
if err != nil {
errStatus = 1
}
err = generateKey(cookieKeyPath)
if err != nil {
errStatus = 1
}
os.Exit(errStatus)
}
log.Info("Initializing...")

View File

@@ -264,12 +264,11 @@ func (c *Collection) PersonObject(ids ...int64) *activitystreams.Person {
p.Name = c.DisplayTitle()
p.Summary = c.Description
if p.Name != "" {
fl := string(unicode.ToLower([]rune(p.Name)[0]))
if isLowerLetter(fl) {
if av := c.AvatarURL(); av != "" {
p.Icon = activitystreams.Image{
Type: "Image",
MediaType: "image/png",
URL: hostName + "/img/avatars/" + fl + ".png",
URL: av,
}
}
}
@@ -287,6 +286,14 @@ func (c *Collection) PersonObject(ids ...int64) *activitystreams.Person {
return p
}
func (c *Collection) AvatarURL() string {
fl := string(unicode.ToLower([]rune(c.DisplayTitle())[0]))
if !isLowerLetter(fl) {
return ""
}
return hostName + "/img/avatars/" + fl + ".png"
}
func (c *Collection) FederatedAPIBase() string {
return hostName + "/"
}

58
keys.go
View File

@@ -1,7 +1,23 @@
package writefreely
import (
"crypto/rand"
"github.com/writeas/web-core/log"
"io/ioutil"
"os"
"path/filepath"
)
const (
keysDir = "keys"
encKeysBytes = 32
)
var (
emailKeyPath = filepath.Join(keysDir, "email.aes256")
cookieAuthKeyPath = filepath.Join(keysDir, "cookies_auth.aes256")
cookieKeyPath = filepath.Join(keysDir, "cookies_enc.aes256")
)
type keychain struct {
@@ -12,20 +28,56 @@ func initKeys(app *app) error {
var err error
app.keys = &keychain{}
app.keys.emailKey, err = ioutil.ReadFile("keys/email.aes256")
app.keys.emailKey, err = ioutil.ReadFile(emailKeyPath)
if err != nil {
return err
}
app.keys.cookieAuthKey, err = ioutil.ReadFile("keys/cookies_auth.aes256")
app.keys.cookieAuthKey, err = ioutil.ReadFile(cookieAuthKeyPath)
if err != nil {
return err
}
app.keys.cookieKey, err = ioutil.ReadFile("keys/cookies_enc.aes256")
app.keys.cookieKey, err = ioutil.ReadFile(cookieKeyPath)
if err != nil {
return err
}
return nil
}
// generateKey generates a key at the given path used for the encryption of
// certain user data. Because user data becomes unrecoverable without these
// keys, this won't overwrite any existing key, and instead outputs a message.
func generateKey(path string) error {
// Check if key file exists
if _, err := os.Stat(path); !os.IsNotExist(err) {
log.Info("%s already exists. rm the file if you understand the consquences.", path)
return nil
}
log.Info("Generating %s.", path)
b, err := generateBytes(encKeysBytes)
if err != nil {
log.Error("FAILED. %s. Run writefreely --gen-keys again.", err)
return err
}
err = ioutil.WriteFile(path, b, 0600)
if err != nil {
log.Error("FAILED writing file: %s", err)
return err
}
log.Info("Success.")
return nil
}
// generateBytes returns securely generated random bytes.
func generateBytes(n int) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
return nil, err
}
return b, nil
}

25
keys.sh
View File

@@ -1,25 +0,0 @@
#!/bin/bash
#
# keys.sh generates keys used for the encryption of certain user data. Because
# user data becomes unrecoverable without these keys, the script and won't
# overwrite any existing keys unless you explicitly delete them.
#
# Generate cookie encryption and authentication keys
if [[ ! -e "$(pwd)/keys/cookies_enc.aes256" ]]; then
dd of=$(pwd)/keys/cookies_enc.aes256 if=/dev/urandom bs=32 count=1
else
echo "cookies key already exists! rm keys/cookies_enc.aes256 if you understand the consquences."
fi
if [[ ! -e "$(pwd)/keys/cookies_auth.aes256" ]]; then
dd of=$(pwd)/keys/cookies_auth.aes256 if=/dev/urandom bs=32 count=1
else
echo "cookies authentication key already exists! rm keys/cookies_auth.aes256 if you understand the consquences."
fi
# Generate email encryption key
if [[ ! -e "$(pwd)/keys/email.aes256" ]]; then
dd of=$(pwd)/keys/email.aes256 if=/dev/urandom bs=32 count=1
else
echo "email key already exists! rm keys/email.aes256 if you understand the consquences."
fi

View File

@@ -1,4 +1,4 @@
Keys
====
Contains keys for encrypting database and session data. Generate necessary keys by running (from the root of the project) `./keys.sh`.
Contains keys for encrypting database and session data. Generate necessary keys by running (from the root of the project) `writefreely --gen-keys`.

View File

@@ -156,7 +156,6 @@ CREATE TABLE IF NOT EXISTS `remoteusers` (
`actor_id` varchar(255) NOT NULL,
`inbox` varchar(255) NOT NULL,
`shared_inbox` varchar(255) NOT NULL,
`followers` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `collection_id` (`actor_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

View File

@@ -20,14 +20,14 @@
<meta name="twitter:card" content="summary">
<meta name="twitter:description" content="{{.Summary}}">
<meta name="twitter:title" content="{{.PlainDisplayTitle}} &mdash; {{if .Collection.Title}}{{.Collection.Title}}{{else}}{{.Collection.Alias}}{{end}}">
{{if gt (len .Images) 0}}<meta name="twitter:image" content="{{index .Images 0}}">{{else}}<meta name="twitter:image" content="https://write.as/img/w-sq-light.png">{{end}}
{{if gt (len .Images) 0}}<meta name="twitter:image" content="{{index .Images 0}}">{{else}}<meta name="twitter:image" content="{{.Collection.AvatarURL}}">{{end}}
<meta property="og:title" content="{{.PlainDisplayTitle}}" />
<meta property="og:description" content="{{.Summary}}" />
<meta property="og:site_name" content="{{.Collection.DisplayTitle}}" />
<meta property="og:type" content="article" />
<meta property="og:url" content="{{.CanonicalURL}}" />
<meta property="og:updated_time" content="{{.Created8601}}" />
{{range .Images}}<meta property="og:image" content="{{.}}" />{{else}}<meta property="og:image" content="https://write.as/img/w-sq-light.png">{{end}}
{{range .Images}}<meta property="og:image" content="{{.}}" />{{else}}<meta property="og:image" content="{{.Collection.AvatarURL}}">{{end}}
<meta property="article:published_time" content="{{.Created8601}}">
{{if .Collection.StyleSheet}}<style type="text/css">{{.Collection.StyleSheetDisplay}}</style>{{end}}
{{if .Collection.RenderMathJax}}

View File

@@ -23,10 +23,12 @@
<meta name="twitter:site" content="@writeas__">
<meta name="twitter:description" content="{{.Tag}} posts on {{.Collection.DisplayTitle}}">
<meta name="twitter:title" content="{{.Tag}} &mdash; {{.Collection.DisplayTitle}}">
<meta name="twitter:image" content="{{.Collection.AvatarURL}}">
<meta property="og:title" content="{{.Tag}} &mdash; {{.Collection.DisplayTitle}}" />
<meta property="og:site_name" content="{{.DisplayTitle}}" />
<meta property="og:type" content="article" />
<meta property="og:url" content="{{.CanonicalURL}}tag:{{.Tag}}" />
<meta property="og:image" content="{{.Collection.AvatarURL}}">
{{if .Collection.StyleSheet}}<style type="text/css">{{.Collection.StyleSheetDisplay}}</style>{{end}}
{{if .Collection.RenderMathJax}}
<script type="text/x-mathjax-config">

View File

@@ -19,12 +19,14 @@
<meta itemprop="description" content="{{.Description}}">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="{{.DisplayTitle}}">
<meta name="twitter:image" content="{{.AvatarURL}}">
<meta name="twitter:description" content="{{.Description}}">
<meta property="og:title" content="{{.DisplayTitle}}" />
<meta property="og:site_name" content="{{.DisplayTitle}}" />
<meta property="og:type" content="article" />
<meta property="og:url" content="{{.CanonicalURL}}" />
<meta property="og:description" content="{{.Description}}" />
<meta property="og:image" content="{{.AvatarURL}}">
{{if .StyleSheet}}<style type="text/css">{{.StyleSheetDisplay}}</style>{{end}}
{{if .RenderMathJax}}
<script type="text/x-mathjax-config">