mirror of https://github.com/writeas/writefreely
commit
6dbc753ecb
@ -0,0 +1,61 @@ |
||||
/* |
||||
* Copyright © 2020 A Bunch Tell LLC. |
||||
* |
||||
* This file is part of WriteFreely. |
||||
* |
||||
* WriteFreely is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, included |
||||
* in the LICENSE file in this source code package. |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"github.com/writeas/writefreely" |
||||
|
||||
"github.com/urfave/cli/v2" |
||||
) |
||||
|
||||
var ( |
||||
cmdConfig cli.Command = cli.Command{ |
||||
Name: "config", |
||||
Usage: "config management tools", |
||||
Subcommands: []*cli.Command{ |
||||
&cmdConfigGenerate, |
||||
&cmdConfigInteractive, |
||||
}, |
||||
} |
||||
|
||||
cmdConfigGenerate cli.Command = cli.Command{ |
||||
Name: "generate", |
||||
Aliases: []string{"gen"}, |
||||
Usage: "Generate a basic configuration", |
||||
Action: genConfigAction, |
||||
} |
||||
|
||||
cmdConfigInteractive cli.Command = cli.Command{ |
||||
Name: "start", |
||||
Usage: "Interactive configuration process", |
||||
Action: interactiveConfigAction, |
||||
Flags: []cli.Flag{ |
||||
&cli.StringFlag{ |
||||
Name: "sections", |
||||
Value: "server db app", |
||||
Usage: "Which sections of the configuration to go through\n" + |
||||
"valid values of sections flag are any combination of 'server', 'db' and 'app' \n" + |
||||
"example: writefreely config start --sections \"db app\"", |
||||
}, |
||||
}, |
||||
} |
||||
) |
||||
|
||||
func genConfigAction(c *cli.Context) error { |
||||
app := writefreely.NewApp(c.String("c")) |
||||
return writefreely.CreateConfig(app) |
||||
} |
||||
|
||||
func interactiveConfigAction(c *cli.Context) error { |
||||
app := writefreely.NewApp(c.String("c")) |
||||
writefreely.DoConfig(app, c.String("sections")) |
||||
return nil |
||||
} |
@ -0,0 +1,50 @@ |
||||
/* |
||||
* Copyright © 2020 A Bunch Tell LLC. |
||||
* |
||||
* This file is part of WriteFreely. |
||||
* |
||||
* WriteFreely is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, included |
||||
* in the LICENSE file in this source code package. |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"github.com/writeas/writefreely" |
||||
|
||||
"github.com/urfave/cli/v2" |
||||
) |
||||
|
||||
var ( |
||||
cmdDB cli.Command = cli.Command{ |
||||
Name: "db", |
||||
Usage: "db management tools", |
||||
Subcommands: []*cli.Command{ |
||||
&cmdDBInit, |
||||
&cmdDBMigrate, |
||||
}, |
||||
} |
||||
|
||||
cmdDBInit cli.Command = cli.Command{ |
||||
Name: "init", |
||||
Usage: "Initialize Database", |
||||
Action: initDBAction, |
||||
} |
||||
|
||||
cmdDBMigrate cli.Command = cli.Command{ |
||||
Name: "migrate", |
||||
Usage: "Migrate Database", |
||||
Action: migrateDBAction, |
||||
} |
||||
) |
||||
|
||||
func initDBAction(c *cli.Context) error { |
||||
app := writefreely.NewApp(c.String("c")) |
||||
return writefreely.CreateSchema(app) |
||||
} |
||||
|
||||
func migrateDBAction(c *cli.Context) error { |
||||
app := writefreely.NewApp(c.String("c")) |
||||
return writefreely.Migrate(app) |
||||
} |
@ -0,0 +1,39 @@ |
||||
/* |
||||
* Copyright © 2020 A Bunch Tell LLC. |
||||
* |
||||
* This file is part of WriteFreely. |
||||
* |
||||
* WriteFreely is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, included |
||||
* in the LICENSE file in this source code package. |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"github.com/writeas/writefreely" |
||||
|
||||
"github.com/urfave/cli/v2" |
||||
) |
||||
|
||||
var ( |
||||
cmdKeys cli.Command = cli.Command{ |
||||
Name: "keys", |
||||
Usage: "key management tools", |
||||
Subcommands: []*cli.Command{ |
||||
&cmdGenerateKeys, |
||||
}, |
||||
} |
||||
|
||||
cmdGenerateKeys cli.Command = cli.Command{ |
||||
Name: "generate", |
||||
Aliases: []string{"gen"}, |
||||
Usage: "Generate encryption and authentication keys", |
||||
Action: genKeysAction, |
||||
} |
||||
) |
||||
|
||||
func genKeysAction(c *cli.Context) error { |
||||
app := writefreely.NewApp(c.String("c")) |
||||
return writefreely.GenerateKeyFiles(app) |
||||
} |
@ -0,0 +1,97 @@ |
||||
/* |
||||
* Copyright © 2020 A Bunch Tell LLC. |
||||
* |
||||
* This file is part of WriteFreely. |
||||
* |
||||
* WriteFreely is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, included |
||||
* in the LICENSE file in this source code package. |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"github.com/writeas/writefreely" |
||||
|
||||
"github.com/urfave/cli/v2" |
||||
) |
||||
|
||||
var ( |
||||
cmdUser cli.Command = cli.Command{ |
||||
Name: "user", |
||||
Usage: "user management tools", |
||||
Subcommands: []*cli.Command{ |
||||
&cmdAddUser, |
||||
&cmdDelUser, |
||||
&cmdResetPass, |
||||
// TODO: possibly add a user list command
|
||||
}, |
||||
} |
||||
|
||||
cmdAddUser cli.Command = cli.Command{ |
||||
Name: "create", |
||||
Usage: "Add new user", |
||||
Aliases: []string{"a", "add"}, |
||||
Flags: []cli.Flag{ |
||||
&cli.BoolFlag{ |
||||
Name: "admin", |
||||
Value: false, |
||||
Usage: "Create admin user", |
||||
}, |
||||
}, |
||||
Action: addUserAction, |
||||
} |
||||
|
||||
cmdDelUser cli.Command = cli.Command{ |
||||
Name: "delete", |
||||
Usage: "Delete user", |
||||
Aliases: []string{"del", "d"}, |
||||
Action: delUserAction, |
||||
} |
||||
|
||||
cmdResetPass cli.Command = cli.Command{ |
||||
Name: "reset-pass", |
||||
Usage: "Reset user's password", |
||||
Aliases: []string{"resetpass", "reset"}, |
||||
Action: resetPassAction, |
||||
} |
||||
) |
||||
|
||||
func addUserAction(c *cli.Context) error { |
||||
credentials := "" |
||||
if c.NArg() > 0 { |
||||
credentials = c.Args().Get(0) |
||||
} else { |
||||
return fmt.Errorf("No user passed. Example: writefreely user add [USER]:[PASSWORD]") |
||||
} |
||||
username, password, err := parseCredentials(credentials) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
app := writefreely.NewApp(c.String("c")) |
||||
return writefreely.CreateUser(app, username, password, c.Bool("admin")) |
||||
} |
||||
|
||||
func delUserAction(c *cli.Context) error { |
||||
username := "" |
||||
if c.NArg() > 0 { |
||||
username = c.Args().Get(0) |
||||
} else { |
||||
return fmt.Errorf("No user passed. Example: writefreely user delete [USER]") |
||||
} |
||||
app := writefreely.NewApp(c.String("c")) |
||||
return writefreely.DoDeleteAccount(app, username) |
||||
} |
||||
|
||||
func resetPassAction(c *cli.Context) error { |
||||
username := "" |
||||
if c.NArg() > 0 { |
||||
username = c.Args().Get(0) |
||||
} else { |
||||
return fmt.Errorf("No user passed. Example: writefreely user reset-pass [USER]") |
||||
} |
||||
app := writefreely.NewApp(c.String("c")) |
||||
return writefreely.ResetPassword(app, username) |
||||
} |
@ -0,0 +1,49 @@ |
||||
/* |
||||
* Copyright © 2020 A Bunch Tell LLC. |
||||
* |
||||
* This file is part of WriteFreely. |
||||
* |
||||
* WriteFreely is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, included |
||||
* in the LICENSE file in this source code package. |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"github.com/writeas/web-core/log" |
||||
"github.com/writeas/writefreely" |
||||
|
||||
"github.com/gorilla/mux" |
||||
"github.com/urfave/cli/v2" |
||||
) |
||||
|
||||
var ( |
||||
cmdServe cli.Command = cli.Command{ |
||||
Name: "serve", |
||||
Aliases: []string{"web"}, |
||||
Usage: "Run web application", |
||||
Action: serveAction, |
||||
} |
||||
) |
||||
|
||||
func serveAction(c *cli.Context) error { |
||||
// Initialize the application
|
||||
app := writefreely.NewApp(c.String("c")) |
||||
var err error |
||||
log.Info("Starting %s...", writefreely.FormatVersion()) |
||||
app, err = writefreely.Initialize(app, c.Bool("debug")) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Set app routes
|
||||
r := mux.NewRouter() |
||||
writefreely.InitRoutes(app, r) |
||||
app.InitStaticRoutes(r) |
||||
|
||||
// Serve the application
|
||||
writefreely.Serve(app, r) |
||||
|
||||
return nil |
||||
} |
@ -0,0 +1,45 @@ |
||||
/* |
||||
* Copyright © 2020 A Bunch Tell LLC. |
||||
* |
||||
* This file is part of WriteFreely. |
||||
* |
||||
* WriteFreely is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, included |
||||
* in the LICENSE file in this source code package. |
||||
*/ |
||||
|
||||
.row.signinbtns { |
||||
justify-content: space-evenly; |
||||
font-size: 1em; |
||||
margin-top: 2em; |
||||
margin-bottom: 1em; |
||||
|
||||
.loginbtn { |
||||
height: 40px; |
||||
} |
||||
|
||||
#writeas-login, #gitlab-login { |
||||
box-sizing: border-box; |
||||
font-size: 17px; |
||||
} |
||||
} |
||||
|
||||
.or { |
||||
text-align: center; |
||||
margin-bottom: 3.5em; |
||||
|
||||
p { |
||||
display: inline-block; |
||||
background-color: white; |
||||
padding: 0 1em; |
||||
} |
||||
|
||||
hr { |
||||
margin-top: -1.6em; |
||||
margin-bottom: 0; |
||||
} |
||||
|
||||
hr.short { |
||||
max-width: 30rem; |
||||
} |
||||
} |
@ -0,0 +1,36 @@ |
||||
package migrations |
||||
|
||||
import ( |
||||
"context" |
||||
"database/sql" |
||||
|
||||
wf_db "github.com/writeas/writefreely/db" |
||||
) |
||||
|
||||
func oauthAttach(db *datastore) error { |
||||
dialect := wf_db.DialectMySQL |
||||
if db.driverName == driverSQLite { |
||||
dialect = wf_db.DialectSQLite |
||||
} |
||||
return wf_db.RunTransactionWithOptions(context.Background(), db.DB, &sql.TxOptions{}, func(ctx context.Context, tx *sql.Tx) error { |
||||
builders := []wf_db.SQLBuilder{ |
||||
dialect. |
||||
AlterTable("oauth_client_states"). |
||||
AddColumn(dialect. |
||||
Column( |
||||
"attach_user_id", |
||||
wf_db.ColumnTypeInteger, |
||||
wf_db.OptionalInt{Set: true, Value: 24}).SetNullable(true)), |
||||
} |
||||
for _, builder := range builders { |
||||
query, err := builder.ToSQL() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if _, err := tx.ExecContext(ctx, query); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
}) |
||||
} |
@ -0,0 +1,45 @@ |
||||
/* |
||||
* Copyright © 2020 A Bunch Tell LLC. |
||||
* |
||||
* This file is part of WriteFreely. |
||||
* |
||||
* WriteFreely is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, included |
||||
* in the LICENSE file in this source code package. |
||||
*/ |
||||
|
||||
package migrations |
||||
|
||||
import ( |
||||
"context" |
||||
"database/sql" |
||||
|
||||
wf_db "github.com/writeas/writefreely/db" |
||||
) |
||||
|
||||
func oauthInvites(db *datastore) error { |
||||
dialect := wf_db.DialectMySQL |
||||
if db.driverName == driverSQLite { |
||||
dialect = wf_db.DialectSQLite |
||||
} |
||||
return wf_db.RunTransactionWithOptions(context.Background(), db.DB, &sql.TxOptions{}, func(ctx context.Context, tx *sql.Tx) error { |
||||
builders := []wf_db.SQLBuilder{ |
||||
dialect. |
||||
AlterTable("oauth_client_states"). |
||||
AddColumn(dialect.Column("invite_code", wf_db.ColumnTypeChar, wf_db.OptionalInt{ |
||||
Set: true, |
||||
Value: 6, |
||||
}).SetNullable(true)), |
||||
} |
||||
for _, builder := range builders { |
||||
query, err := builder.ToSQL() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if _, err := tx.ExecContext(ctx, query); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
}) |
||||
} |
@ -0,0 +1,37 @@ |
||||
/* |
||||
* Copyright © 2020 A Bunch Tell LLC. |
||||
* |
||||
* This file is part of WriteFreely. |
||||
* |
||||
* WriteFreely is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, included |
||||
* in the LICENSE file in this source code package. |
||||
*/ |
||||
|
||||
package migrations |
||||
|
||||
func optimizeDrafts(db *datastore) error { |
||||
t, err := db.Begin() |
||||
if err != nil { |
||||
t.Rollback() |
||||
return err |
||||
} |
||||
|
||||
if db.driverName == driverSQLite { |
||||
_, err = t.Exec(`CREATE INDEX key_owner_post_id ON posts (owner_id, id)`) |
||||
} else { |
||||
_, err = t.Exec(`ALTER TABLE posts ADD INDEX(owner_id, id)`) |
||||
} |
||||
if err != nil { |
||||
t.Rollback() |
||||
return err |
||||
} |
||||
|
||||
err = t.Commit() |
||||
if err != nil { |
||||
t.Rollback() |
||||
return err |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -0,0 +1,115 @@ |
||||
package writefreely |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"net/http" |
||||
"net/url" |
||||
"strings" |
||||
) |
||||
|
||||
type gitlabOauthClient struct { |
||||
ClientID string |
||||
ClientSecret string |
||||
AuthLocation string |
||||
ExchangeLocation string |
||||
InspectLocation string |
||||
CallbackLocation string |
||||
HttpClient HttpClient |
||||
} |
||||
|
||||
var _ oauthClient = gitlabOauthClient{} |
||||
|
||||
const ( |
||||
gitlabHost = "https://gitlab.com" |
||||
gitlabDisplayName = "GitLab" |
||||
) |
||||
|
||||
func (c gitlabOauthClient) GetProvider() string { |
||||
return "gitlab" |
||||
} |
||||
|
||||
func (c gitlabOauthClient) GetClientID() string { |
||||
return c.ClientID |
||||
} |
||||
|
||||
func (c gitlabOauthClient) GetCallbackLocation() string { |
||||
return c.CallbackLocation |
||||
} |
||||
|
||||
func (c gitlabOauthClient) buildLoginURL(state string) (string, error) { |
||||
u, err := url.Parse(c.AuthLocation) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
q := u.Query() |
||||
q.Set("client_id", c.ClientID) |
||||
q.Set("redirect_uri", c.CallbackLocation) |
||||
q.Set("response_type", "code") |
||||
q.Set("state", state) |
||||
q.Set("scope", "read_user") |
||||
u.RawQuery = q.Encode() |
||||
return u.String(), nil |
||||
} |
||||
|
||||
func (c gitlabOauthClient) exchangeOauthCode(ctx context.Context, code string) (*TokenResponse, error) { |
||||
form := url.Values{} |
||||
form.Add("grant_type", "authorization_code") |
||||
form.Add("redirect_uri", c.CallbackLocation) |
||||
form.Add("scope", "read_user") |
||||
form.Add("code", code) |
||||
req, err := http.NewRequest("POST", c.ExchangeLocation, strings.NewReader(form.Encode())) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
req.WithContext(ctx) |
||||
req.Header.Set("User-Agent", "writefreely") |
||||
req.Header.Set("Accept", "application/json") |
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
||||
req.SetBasicAuth(c.ClientID, c.ClientSecret) |
||||
|
||||
resp, err := c.HttpClient.Do(req) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if resp.StatusCode != http.StatusOK { |
||||
return nil, errors.New("unable to exchange code for access token") |
||||
} |
||||
|
||||
var tokenResponse TokenResponse |
||||
if err := limitedJsonUnmarshal(resp.Body, tokenRequestMaxLen, &tokenResponse); err != nil { |
||||
return nil, err |
||||
} |
||||
if tokenResponse.Error != "" { |
||||
return nil, errors.New(tokenResponse.Error) |
||||
} |
||||
return &tokenResponse, nil |
||||
} |
||||
|
||||
func (c gitlabOauthClient) inspectOauthAccessToken(ctx context.Context, accessToken string) (*InspectResponse, error) { |
||||
req, err := http.NewRequest("GET", c.InspectLocation, nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
req.WithContext(ctx) |
||||
req.Header.Set("User-Agent", "writefreely") |
||||
req.Header.Set("Accept", "application/json") |
||||
req.Header.Set("Authorization", "Bearer "+accessToken) |
||||
|
||||
resp, err := c.HttpClient.Do(req) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if resp.StatusCode != http.StatusOK { |
||||
return nil, errors.New("unable to inspect access token") |
||||
} |
||||
|
||||
var inspectResponse InspectResponse |
||||
if err := limitedJsonUnmarshal(resp.Body, infoRequestMaxLen, &inspectResponse); err != nil { |
||||
return nil, err |
||||
} |
||||
if inspectResponse.Error != "" { |
||||
return nil, errors.New(inspectResponse.Error) |
||||
} |
||||
return &inspectResponse, nil |
||||
} |
@ -0,0 +1,7 @@ |
||||
{{define "head"}}<title>Temporarily Unavailable — {{.SiteMetaName}}</title>{{end}} |
||||
{{define "content"}} |
||||
<div class="error-page"> |
||||
<p class="msg">The words aren't coming to me. 🗅</p> |
||||
<p>We couldn't serve this page due to high server load. This should only be temporary.</p> |
||||
</div> |
||||
{{end}} |
@ -0,0 +1,37 @@ |
||||
#!/bin/bash |
||||
# |
||||
# Copyright © 2020 A Bunch Tell LLC. |
||||
# |
||||
# This file is part of WriteFreely. |
||||
# |
||||
# WriteFreely is free software: you can redistribute it and/or modify |
||||
# it under the terms of the GNU Affero General Public License, included |
||||
# in the LICENSE file in this source code package. |
||||
# |
||||
############################################################################### |
||||
# |
||||
# WriteFreely CSS invalidation script |
||||
# |
||||
# usage: ./invalidate-css.sh <build-directory> |
||||
# |
||||
# This script provides an automated way to invalidate stylesheets cached in the |
||||
# browser. It uses the last git commit hashes of the most frequently modified |
||||
# LESS files in the project and appends them to the stylesheet `href` in all |
||||
# template files. |
||||
# |
||||
# This is designed to be used when building a WriteFreely release. |
||||
# |
||||
############################################################################### |
||||
|
||||
# Get parent build directory from first argument |
||||
buildDir=$1 |
||||
|
||||
# Get short hash of each primary LESS file's last commit |
||||
cssHash=$(git log -n 1 --pretty=format:%h -- less/core.less) |
||||
cssNewHash=$(git log -n 1 --pretty=format:%h -- less/new-core.less) |
||||
cssPadHash=$(git log -n 1 --pretty=format:%h -- less/pad.less) |
||||
|
||||
echo "Adding write.css version ($cssHash $cssNewHash $cssPadHash) to .tmpl files..." |
||||
cd "$buildDir/templates" || exit 1 |
||||
find . -type f -name "*.tmpl" -print0 | xargs -0 sed -i "s/write.css/write.css?${cssHash}${cssNewHash}${cssPadHash}/g" |
||||
find . -type f -name "*.tmpl" -print0 | xargs -0 sed -i "s/{{.Theme}}.css/{{.Theme}}.css?${cssHash}${cssNewHash}${cssPadHash}/g" |
After Width: | Height: | Size: 1005 B |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.8 KiB |
Loading…
Reference in new issue