mirror of https://github.com/writeas/writefreely
Merge branch 'develop' of https://github.com/writeas/writefreely into fix-youtube-query-parameters
commit
79715891fb
@ -1,3 +0,0 @@ |
|||||||
[submodule "static/js/mathjax"] |
|
||||||
path = static/js/mathjax |
|
||||||
url = https://github.com/mathjax/MathJax.git |
|
@ -0,0 +1,114 @@ |
|||||||
|
package writefreely |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"errors" |
||||||
|
"net/http" |
||||||
|
"net/url" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
type genericOauthClient struct { |
||||||
|
ClientID string |
||||||
|
ClientSecret string |
||||||
|
AuthLocation string |
||||||
|
ExchangeLocation string |
||||||
|
InspectLocation string |
||||||
|
CallbackLocation string |
||||||
|
HttpClient HttpClient |
||||||
|
} |
||||||
|
|
||||||
|
var _ oauthClient = genericOauthClient{} |
||||||
|
|
||||||
|
const ( |
||||||
|
genericOauthDisplayName = "OAuth" |
||||||
|
) |
||||||
|
|
||||||
|
func (c genericOauthClient) GetProvider() string { |
||||||
|
return "generic" |
||||||
|
} |
||||||
|
|
||||||
|
func (c genericOauthClient) GetClientID() string { |
||||||
|
return c.ClientID |
||||||
|
} |
||||||
|
|
||||||
|
func (c genericOauthClient) GetCallbackLocation() string { |
||||||
|
return c.CallbackLocation |
||||||
|
} |
||||||
|
|
||||||
|
func (c genericOauthClient) buildLoginURL(state string) (string, error) { |
||||||
|
u, err := url.Parse(c.AuthLocation) |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
q := u.Query() |
||||||
|
q.Set("client_id", c.ClientID) |
||||||
|
q.Set("redirect_uri", c.CallbackLocation) |
||||||
|
q.Set("response_type", "code") |
||||||
|
q.Set("state", state) |
||||||
|
q.Set("scope", "read_user") |
||||||
|
u.RawQuery = q.Encode() |
||||||
|
return u.String(), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c genericOauthClient) exchangeOauthCode(ctx context.Context, code string) (*TokenResponse, error) { |
||||||
|
form := url.Values{} |
||||||
|
form.Add("grant_type", "authorization_code") |
||||||
|
form.Add("redirect_uri", c.CallbackLocation) |
||||||
|
form.Add("scope", "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", ServerUserAgent("")) |
||||||
|
req.Header.Set("Accept", "application/json") |
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
||||||
|
req.SetBasicAuth(c.ClientID, c.ClientSecret) |
||||||
|
|
||||||
|
resp, err := c.HttpClient.Do(req) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
if resp.StatusCode != http.StatusOK { |
||||||
|
return nil, errors.New("unable to exchange code for access token") |
||||||
|
} |
||||||
|
|
||||||
|
var tokenResponse TokenResponse |
||||||
|
if err := limitedJsonUnmarshal(resp.Body, tokenRequestMaxLen, &tokenResponse); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
if tokenResponse.Error != "" { |
||||||
|
return nil, errors.New(tokenResponse.Error) |
||||||
|
} |
||||||
|
return &tokenResponse, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c genericOauthClient) inspectOauthAccessToken(ctx context.Context, accessToken string) (*InspectResponse, error) { |
||||||
|
req, err := http.NewRequest("GET", c.InspectLocation, nil) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
req.WithContext(ctx) |
||||||
|
req.Header.Set("User-Agent", ServerUserAgent("")) |
||||||
|
req.Header.Set("Accept", "application/json") |
||||||
|
req.Header.Set("Authorization", "Bearer "+accessToken) |
||||||
|
|
||||||
|
resp, err := c.HttpClient.Do(req) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
if resp.StatusCode != http.StatusOK { |
||||||
|
return nil, errors.New("unable to inspect access token") |
||||||
|
} |
||||||
|
|
||||||
|
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,114 @@ |
|||||||
|
package writefreely |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"errors" |
||||||
|
"net/http" |
||||||
|
"net/url" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
type giteaOauthClient struct { |
||||||
|
ClientID string |
||||||
|
ClientSecret string |
||||||
|
AuthLocation string |
||||||
|
ExchangeLocation string |
||||||
|
InspectLocation string |
||||||
|
CallbackLocation string |
||||||
|
HttpClient HttpClient |
||||||
|
} |
||||||
|
|
||||||
|
var _ oauthClient = giteaOauthClient{} |
||||||
|
|
||||||
|
const ( |
||||||
|
giteaDisplayName = "Gitea" |
||||||
|
) |
||||||
|
|
||||||
|
func (c giteaOauthClient) GetProvider() string { |
||||||
|
return "gitea" |
||||||
|
} |
||||||
|
|
||||||
|
func (c giteaOauthClient) GetClientID() string { |
||||||
|
return c.ClientID |
||||||
|
} |
||||||
|
|
||||||
|
func (c giteaOauthClient) GetCallbackLocation() string { |
||||||
|
return c.CallbackLocation |
||||||
|
} |
||||||
|
|
||||||
|
func (c giteaOauthClient) buildLoginURL(state string) (string, error) { |
||||||
|
u, err := url.Parse(c.AuthLocation) |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
q := u.Query() |
||||||
|
q.Set("client_id", c.ClientID) |
||||||
|
q.Set("redirect_uri", c.CallbackLocation) |
||||||
|
q.Set("response_type", "code") |
||||||
|
q.Set("state", state) |
||||||
|
// q.Set("scope", "read_user")
|
||||||
|
u.RawQuery = q.Encode() |
||||||
|
return u.String(), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c giteaOauthClient) exchangeOauthCode(ctx context.Context, code string) (*TokenResponse, error) { |
||||||
|
form := url.Values{} |
||||||
|
form.Add("grant_type", "authorization_code") |
||||||
|
form.Add("redirect_uri", c.CallbackLocation) |
||||||
|
// form.Add("scope", "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", ServerUserAgent("")) |
||||||
|
req.Header.Set("Accept", "application/json") |
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
||||||
|
req.SetBasicAuth(c.ClientID, c.ClientSecret) |
||||||
|
|
||||||
|
resp, err := c.HttpClient.Do(req) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
if resp.StatusCode != http.StatusOK { |
||||||
|
return nil, errors.New("unable to exchange code for access token") |
||||||
|
} |
||||||
|
|
||||||
|
var tokenResponse TokenResponse |
||||||
|
if err := limitedJsonUnmarshal(resp.Body, tokenRequestMaxLen, &tokenResponse); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
if tokenResponse.Error != "" { |
||||||
|
return nil, errors.New(tokenResponse.Error) |
||||||
|
} |
||||||
|
return &tokenResponse, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c giteaOauthClient) inspectOauthAccessToken(ctx context.Context, accessToken string) (*InspectResponse, error) { |
||||||
|
req, err := http.NewRequest("GET", c.InspectLocation, nil) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
req.WithContext(ctx) |
||||||
|
req.Header.Set("User-Agent", ServerUserAgent("")) |
||||||
|
req.Header.Set("Accept", "application/json") |
||||||
|
req.Header.Set("Authorization", "Bearer "+accessToken) |
||||||
|
|
||||||
|
resp, err := c.HttpClient.Do(req) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
if resp.StatusCode != http.StatusOK { |
||||||
|
return nil, errors.New("unable to inspect access token") |
||||||
|
} |
||||||
|
|
||||||
|
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,38 @@ |
|||||||
|
package writefreely |
||||||
|
|
||||||
|
import ( |
||||||
|
"net/http" |
||||||
|
"net/http/httptest" |
||||||
|
"strings" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/gorilla/mux" |
||||||
|
) |
||||||
|
|
||||||
|
func TestCacheControlForStaticFiles(t *testing.T) { |
||||||
|
app := NewApp("testdata/config.ini") |
||||||
|
if err := app.LoadConfig(); err != nil { |
||||||
|
t.Fatalf("Could not create an app; %v", err) |
||||||
|
} |
||||||
|
router := mux.NewRouter() |
||||||
|
app.InitStaticRoutes(router) |
||||||
|
|
||||||
|
rec := httptest.NewRecorder() |
||||||
|
req := httptest.NewRequest("GET", "/style.css", nil) |
||||||
|
router.ServeHTTP(rec, req) |
||||||
|
if code := rec.Result().StatusCode; code != http.StatusOK { |
||||||
|
t.Fatalf("Could not get /style.css, got HTTP status %d", code) |
||||||
|
} |
||||||
|
actual := rec.Result().Header.Get("Cache-Control") |
||||||
|
|
||||||
|
expectedDirectives := []string{ |
||||||
|
"public", |
||||||
|
"max-age", |
||||||
|
"immutable", |
||||||
|
} |
||||||
|
for _, expected := range expectedDirectives { |
||||||
|
if !strings.Contains(actual, expected) { |
||||||
|
t.Errorf("Expected Cache-Control header to contain '%s', but was '%s'", expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 2.7 KiB |
@ -1 +0,0 @@ |
|||||||
Subproject commit 419b0a6eee7eefc0f85e47f7d4f8227ec28b8e57 |
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,34 @@ |
|||||||
|
var menuItems = document.querySelectorAll('li.has-submenu'); |
||||||
|
var menuTimer; |
||||||
|
function closeMenu($menu) { |
||||||
|
$menu.querySelector('a').setAttribute('aria-expanded', "false"); |
||||||
|
$menu.className = "has-submenu"; |
||||||
|
} |
||||||
|
Array.prototype.forEach.call(menuItems, function(el, i){ |
||||||
|
el.addEventListener("mouseover", function(event){ |
||||||
|
let $menu = document.querySelectorAll(".has-submenu.open"); |
||||||
|
if ($menu.length > 0) { |
||||||
|
closeMenu($menu[0]); |
||||||
|
} |
||||||
|
this.className = "has-submenu open"; |
||||||
|
this.querySelector('a').setAttribute('aria-expanded', "true"); |
||||||
|
clearTimeout(menuTimer); |
||||||
|
}); |
||||||
|
el.addEventListener("mouseout", function(event){ |
||||||
|
menuTimer = setTimeout(function(event){ |
||||||
|
let $menu = document.querySelector(".has-submenu.open"); |
||||||
|
closeMenu($menu); |
||||||
|
}, 500); |
||||||
|
}); |
||||||
|
el.querySelector('a').addEventListener("click", function(event){ |
||||||
|
if (this.parentNode.className == "has-submenu") { |
||||||
|
this.parentNode.className = "has-submenu open"; |
||||||
|
this.setAttribute('aria-expanded', "true"); |
||||||
|
} else { |
||||||
|
this.parentNode.className = "has-submenu"; |
||||||
|
this.setAttribute('aria-expanded', "false"); |
||||||
|
} |
||||||
|
event.preventDefault(); |
||||||
|
return false; |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,37 @@ |
|||||||
|
{{define "oauth-buttons"}} |
||||||
|
{{ if or .SlackEnabled .WriteAsEnabled .GitLabEnabled .GiteaEnabled .GenericEnabled }} |
||||||
|
<div class="row content-container signinbtns"> |
||||||
|
{{ if .SlackEnabled }} |
||||||
|
<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 .WriteAsEnabled }} |
||||||
|
<a class="btn cta loginbtn" id="writeas-login" href="/oauth/write.as"> |
||||||
|
<img src="/img/mark/writeas-white.png" /> |
||||||
|
Sign in with <strong>Write.as</strong> |
||||||
|
</a> |
||||||
|
{{ end }} |
||||||
|
{{ if .GitLabEnabled }} |
||||||
|
<a class="btn cta loginbtn" id="gitlab-login" href="/oauth/gitlab"> |
||||||
|
<img src="/img/mark/gitlab.png" /> |
||||||
|
Sign in with <strong>{{.GitLabDisplayName}}</strong> |
||||||
|
</a> |
||||||
|
{{ end }} |
||||||
|
{{ if .GiteaEnabled }} |
||||||
|
<a class="btn cta loginbtn" id="gitea-login" href="/oauth/gitea"> |
||||||
|
<img src="/img/mark/gitea.png" /> |
||||||
|
Sign in with <strong>{{.GiteaDisplayName}}</strong> |
||||||
|
</a> |
||||||
|
{{ end }} |
||||||
|
{{ if .GenericEnabled }} |
||||||
|
<a class="btn cta loginbtn" id="generic-oauth-login" href="/oauth/generic">Sign in with <strong>{{.GenericDisplayName}}</strong></a> |
||||||
|
{{ end }} |
||||||
|
</div> |
||||||
|
|
||||||
|
{{if not .DisablePasswordAuth}} |
||||||
|
<div class="or"> |
||||||
|
<p>or</p> |
||||||
|
<hr class="short" /> |
||||||
|
</div> |
||||||
|
{{end}} |
||||||
|
{{ end }} |
||||||
|
{{end}} |
@ -0,0 +1,16 @@ |
|||||||
|
{{define "collection-breadcrumbs"}} |
||||||
|
{{if and .Collection (not .SingleUser)}}<nav id="org-nav"><a href="/me/c/">Blogs</a> / <a class="coll-name" href="/{{.Collection.Alias}}/">{{.Collection.DisplayTitle}}</a></nav>{{end}} |
||||||
|
{{end}} |
||||||
|
|
||||||
|
{{define "collection-nav"}} |
||||||
|
{{if not .SingleUser}} |
||||||
|
<header class="admin"> |
||||||
|
<nav class="pager"> |
||||||
|
{{if .CanPost}}<a href="{{if .SingleUser}}/me/new{{else}}/#{{.Alias}}{{end}}" class="btn gentlecta">New Post</a>{{end}} |
||||||
|
<a href="/me/c/{{.Alias}}" {{if and (hasPrefix .Path "/me/c/") (hasSuffix .Path .Alias)}}class="selected"{{end}}>Customize</a> |
||||||
|
<a href="/me/c/{{.Alias}}/stats" {{if hasSuffix .Path "/stats"}}class="selected"{{end}}>Stats</a> |
||||||
|
<a href="{{if .SingleUser}}/{{else}}/{{.Alias}}/{{end}}">View Blog →</a> |
||||||
|
</nav> |
||||||
|
</header> |
||||||
|
{{end}} |
||||||
|
{{end}} |
@ -0,0 +1 @@ |
|||||||
|
!config.ini |
@ -0,0 +1,2 @@ |
|||||||
|
[server] |
||||||
|
static_parent_dir = testdata |
@ -0,0 +1,3 @@ |
|||||||
|
body { |
||||||
|
background-color: lightblue; |
||||||
|
} |
Loading…
Reference in new issue