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