Page Menu
Home
Musing Studio
Search
Configure Global Search
Log In
Files
F10387214
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
12 KB
Subscribers
None
View Options
diff --git a/config/config.go b/config/config.go
index 78892bf..a616401 100644
--- a/config/config.go
+++ b/config/config.go
@@ -1,217 +1,227 @@
/*
* Copyright © 2018-2019 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 config holds and assists in the configuration of a writefreely instance.
package config
import (
"strings"
"gopkg.in/ini.v1"
)
const (
// FileName is the default configuration file name
FileName = "config.ini"
UserNormal UserType = "user"
UserAdmin = "admin"
)
type (
UserType string
// ServerCfg holds values that affect how the HTTP server runs
ServerCfg struct {
HiddenHost string `ini:"hidden_host"`
Port int `ini:"port"`
Bind string `ini:"bind"`
TLSCertPath string `ini:"tls_cert_path"`
TLSKeyPath string `ini:"tls_key_path"`
Autocert bool `ini:"autocert"`
TemplatesParentDir string `ini:"templates_parent_dir"`
StaticParentDir string `ini:"static_parent_dir"`
PagesParentDir string `ini:"pages_parent_dir"`
KeysParentDir string `ini:"keys_parent_dir"`
HashSeed string `ini:"hash_seed"`
Dev bool `ini:"-"`
}
// DatabaseCfg holds values that determine how the application connects to a datastore
DatabaseCfg struct {
Type string `ini:"type"`
FileName string `ini:"filename"`
User string `ini:"username"`
Password string `ini:"password"`
Database string `ini:"database"`
Host string `ini:"host"`
Port int `ini:"port"`
}
WriteAsOauthCfg struct {
ClientID string `ini:"client_id"`
ClientSecret string `ini:"client_secret"`
AuthLocation string `ini:"auth_location"`
TokenLocation string `ini:"token_location"`
InspectLocation string `ini:"inspect_location"`
CallbackProxy string `ini:"callback_proxy"`
CallbackProxyAPI string `ini:"callback_proxy_api"`
}
SlackOauthCfg struct {
ClientID string `ini:"client_id"`
ClientSecret string `ini:"client_secret"`
TeamID string `ini:"team_id"`
CallbackProxy string `ini:"callback_proxy"`
CallbackProxyAPI string `ini:"callback_proxy_api"`
}
// AppCfg holds values that affect how the application functions
AppCfg struct {
SiteName string `ini:"site_name"`
SiteDesc string `ini:"site_description"`
Host string `ini:"host"`
// Site appearance
Theme string `ini:"theme"`
Editor string `ini:"editor"`
JSDisabled bool `ini:"disable_js"`
WebFonts bool `ini:"webfonts"`
Landing string `ini:"landing"`
SimpleNav bool `ini:"simple_nav"`
WFModesty bool `ini:"wf_modesty"`
// Site functionality
Chorus bool `ini:"chorus"`
Forest bool `ini:"forest"` // The admin cares about the forest, not the trees. Hide unnecessary technical info.
DisableDrafts bool `ini:"disable_drafts"`
// Users
SingleUser bool `ini:"single_user"`
OpenRegistration bool `ini:"open_registration"`
MinUsernameLen int `ini:"min_username_len"`
MaxBlogs int `ini:"max_blogs"`
// Federation
Federation bool `ini:"federation"`
PublicStats bool `ini:"public_stats"`
// Access
Private bool `ini:"private"`
// Additional functions
LocalTimeline bool `ini:"local_timeline"`
UserInvites string `ini:"user_invites"`
// Defaults
DefaultVisibility string `ini:"default_visibility"`
// Check for Updates
UpdateChecks bool `ini:"update_checks"`
}
// Config holds the complete configuration for running a writefreely instance
Config struct {
Server ServerCfg `ini:"server"`
Database DatabaseCfg `ini:"database"`
App AppCfg `ini:"app"`
SlackOauth SlackOauthCfg `ini:"oauth.slack"`
WriteAsOauth WriteAsOauthCfg `ini:"oauth.writeas"`
}
)
// New creates a new Config with sane defaults
func New() *Config {
c := &Config{
Server: ServerCfg{
Port: 8080,
Bind: "localhost", /* IPV6 support when not using localhost? */
},
App: AppCfg{
Host: "http://localhost:8080",
Theme: "write",
WebFonts: true,
SingleUser: true,
MinUsernameLen: 3,
MaxBlogs: 1,
Federation: true,
PublicStats: true,
},
}
c.UseMySQL(true)
return c
}
// UseMySQL resets the Config's Database to use default values for a MySQL setup.
func (cfg *Config) UseMySQL(fresh bool) {
cfg.Database.Type = "mysql"
if fresh {
cfg.Database.Host = "localhost"
cfg.Database.Port = 3306
}
}
// UseSQLite resets the Config's Database to use default values for a SQLite setup.
func (cfg *Config) UseSQLite(fresh bool) {
cfg.Database.Type = "sqlite3"
if fresh {
cfg.Database.FileName = "writefreely.db"
}
}
// IsSecureStandalone returns whether or not the application is running as a
// standalone server with TLS enabled.
func (cfg *Config) IsSecureStandalone() bool {
return cfg.Server.Port == 443 && cfg.Server.TLSCertPath != "" && cfg.Server.TLSKeyPath != ""
}
func (ac *AppCfg) LandingPath() string {
if !strings.HasPrefix(ac.Landing, "/") {
return "/" + ac.Landing
}
return ac.Landing
}
+func (ac AppCfg) SignupPath() string {
+ if !ac.OpenRegistration {
+ return ""
+ }
+ if ac.Chorus || ac.Private || (ac.Landing != "" && ac.Landing != "/") {
+ return "/signup"
+ }
+ return "/"
+}
+
// Load reads the given configuration file, then parses and returns it as a Config.
func Load(fname string) (*Config, error) {
if fname == "" {
fname = FileName
}
cfg, err := ini.Load(fname)
if err != nil {
return nil, err
}
// Parse INI file
uc := &Config{}
err = cfg.MapTo(uc)
if err != nil {
return nil, err
}
return uc, nil
}
// Save writes the given Config to the given file.
func Save(uc *Config, fname string) error {
cfg := ini.Empty()
err := ini.ReflectFrom(cfg, uc)
if err != nil {
return err
}
if fname == "" {
fname = FileName
}
return cfg.SaveTo(fname)
}
diff --git a/pages/login.tmpl b/pages/login.tmpl
index 345b171..79875c6 100644
--- a/pages/login.tmpl
+++ b/pages/login.tmpl
@@ -1,77 +1,77 @@
{{define "head"}}<title>Log in — {{.SiteName}}</title>
<meta name="description" content="Log in to {{.SiteName}}.">
<meta itemprop="description" content="Log in to {{.SiteName}}.">
<style>
input{margin-bottom:0.5em;}
.or {
text-align: center;
margin-bottom: 3.5em;
}
.or p {
display: inline-block;
background-color: white;
padding: 0 1em;
}
.or hr {
margin-top: -1.6em;
margin-bottom: 0;
}
hr.short {
max-width: 30rem;
}
.row.signinbtns {
justify-content: space-evenly;
font-size: 1em;
margin-top: 3em;
margin-bottom: 2em;
}
.loginbtn {
height: 40px;
}
#writeas-login {
box-sizing: border-box;
font-size: 17px;
}
</style>
{{end}}
{{define "content"}}
<div class="tight content-container">
<h1>Log in to {{.SiteName}}</h1>
{{if .Flashes}}<ul class="errors">
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}}
</ul>{{end}}
{{ if or .OauthSlack .OauthWriteAs }}
<div class="row content-container signinbtns">
{{ if .OauthSlack }}
<a class="loginbtn" href="/oauth/slack"><img alt="Sign in with Slack" height="40" width="172" src="/img/sign_in_with_slack.png" srcset="/img/sign_in_with_slack.png 1x, /img/sign_in_with_slack@2x.png 2x" /></a>
{{ end }}
{{ if .OauthWriteAs }}
<a class="btn cta loginbtn" id="writeas-login" href="/oauth/write.as">Sign in with <strong>Write.as</strong></a>
{{ end }}
</div>
<div class="or">
<p>or</p>
<hr class="short" />
</div>
{{ end }}
<form action="/auth/login" method="post" style="text-align: center;margin-top:1em;" onsubmit="disableSubmit()">
<input type="text" name="alias" placeholder="Username" value="{{.LoginUsername}}" {{if not .LoginUsername}}autofocus{{end}} /><br />
<input type="password" name="pass" placeholder="Password" {{if .LoginUsername}}autofocus{{end}} /><br />
{{if .To}}<input type="hidden" name="to" value="{{.To}}" />{{end}}
<input type="submit" id="btn-login" value="Login" />
</form>
- {{if and (not .SingleUser) .OpenRegistration}}<p style="text-align:center;font-size:0.9em;margin:3em auto;max-width:26em;">{{if .Message}}{{.Message}}{{else}}<em>No account yet?</em> <a href="/">Sign up</a> to start a blog.{{end}}</p>{{end}}
+ {{if and (not .SingleUser) .OpenRegistration}}<p style="text-align:center;font-size:0.9em;margin:3em auto;max-width:26em;">{{if .Message}}{{.Message}}{{else}}<em>No account yet?</em> <a href="{{.SignupPath}}">Sign up</a> to start a blog.{{end}}</p>{{end}}
<script type="text/javascript">
function disableSubmit() {
var $btn = document.getElementById("btn-login");
$btn.value = "Logging in...";
$btn.disabled = true;
}
</script>
{{end}}
diff --git a/templates/base.tmpl b/templates/base.tmpl
index ef44121..556dfba 100644
--- a/templates/base.tmpl
+++ b/templates/base.tmpl
@@ -1,92 +1,92 @@
{{define "base"}}<!DOCTYPE HTML>
<html>
<head>
{{ template "head" . }}
<link rel="stylesheet" type="text/css" href="{{.Host}}/css/{{.Theme}}.css" />
<link rel="shortcut icon" href="{{.Host}}/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="application-name" content="{{.SiteName}}">
<meta name="application-url" content="{{.Host}}">
<meta property="og:site_name" content="{{.SiteName}}" />
</head>
<body {{template "body-attrs" .}}>
<div id="overlay"></div>
<header>
{{ if .Chorus }}<nav id="full-nav">
<div class="left-side">
<h2><a href="/">{{.SiteName}}</a></h2>
</div>
{{ else }}
<h2><a href="/">{{.SiteName}}</a></h2>
{{ end }}
{{if not .SingleUser}}
<nav id="user-nav">
{{if .Username}}
<nav class="dropdown-nav">
<ul><li><a>{{.Username}}</a> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /><ul>
{{if .IsAdmin}}<li><a href="/admin">Admin dashboard</a></li>{{end}}
<li><a href="/me/settings">Account settings</a></li>
<li><a href="/me/export">Export</a></li>
{{if .CanInvite}}<li><a href="/me/invites">Invite people</a></li>{{end}}
<li class="separator"><hr /></li>
<li><a href="/me/logout">Log out</a></li>
</ul></li>
</ul>
</nav>
{{end}}
<nav class="tabs">
{{ if and .SimpleNav (not .SingleUser) }}
{{if and (and .LocalTimeline .CanViewReader) .Chorus}}<a href="/"{{if eq .Path "/"}} class="selected"{{end}}>Home</a>{{end}}
{{ end }}
{{if or .Chorus (not .Username)}}<a href="/about"{{if eq .Path "/about"}} class="selected"{{end}}>About</a>{{end}}
{{ if not .SingleUser }}
{{ if .Username }}
{{if or (not .Chorus) (gt .MaxBlogs 1)}}<a href="/me/c/"{{if eq .Path "/me/c/"}} class="selected"{{end}}>Blogs</a>{{end}}
{{if and (and .Chorus (eq .MaxBlogs 1)) .Username}}<a href="/{{.Username}}/"{{if eq .Path (printf "/%s/" .Username)}} class="selected"{{end}}>My Posts</a>{{end}}
{{if not .DisableDrafts}}<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>Drafts</a>{{end}}
{{ end }}
{{if and (and .LocalTimeline .CanViewReader) (not .Chorus)}}<a href="/read"{{if eq .Path "/read"}} class="selected"{{end}}>Reader</a>{{end}}
- {{if and (and (and .Chorus .OpenRegistration) (not .Username)) (or (not .Private) (ne .Landing ""))}}<a href="/signup"{{if eq .Path "/signup"}} class="selected"{{end}}>Sign up</a>{{end}}
+ {{if eq .SignupPath "/signup"}}<a href="/signup"{{if eq .Path "/signup"}} class="selected"{{end}}>Sign up</a>{{end}}
{{if and (not .Username) (not .Private)}}<a href="/login"{{if eq .Path "/login"}} class="selected"{{end}}>Log in</a>{{else if .SimpleNav}}<a href="/me/logout">Log out</a>{{end}}
{{ end }}
</nav>
{{if .Chorus}}{{if .Username}}<div class="right-side" style="font-size: 0.86em;">
<a class="simple-btn" href="/new">New Post</a>
</div>{{end}}
</nav>
{{end}}
</nav>
{{end}}
</header>
<div id="official-writing">
{{ template "content" . }}
</div>
{{ template "footer" . }}
{{if not .JSDisabled}}
<script type="text/javascript">
{{if .WebFonts}}
try { // Google Fonts
WebFontConfig = {
custom: { families: [ 'Lora:400,700:latin', 'Open+Sans:400,700:latin' ], urls: [ '/css/fonts.css' ] }
};
(function() {
var wf = document.createElement('script');
wf.src = '/js/webfont.js';
wf.type = 'text/javascript';
wf.async = 'true';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(wf, s);
})();
} catch (e) { /* ¯\_(ツ)_/¯ */ }
{{end}}
</script>
{{else}}
{{if .WebFonts}}<link href="{{.Host}}/css/fonts.css" rel="stylesheet" type="text/css" />{{end}}
{{end}}
</body>
</html>{{end}}
{{define "body-attrs"}}{{end}}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Nov 25, 2:38 AM (1 d, 12 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3104599
Attached To
rWF WriteFreely
Event Timeline
Log In to Comment