diff --git a/account.go b/account.go index fbe5ad0..236dfb3 100644 --- a/account.go +++ b/account.go @@ -304,32 +304,18 @@ func viewLogin(app *App, w http.ResponseWriter, r *http.Request) error { p := &struct { page.StaticPage - To string - Message template.HTML - Flashes []template.HTML - LoginUsername string - OauthSlack bool - OauthWriteAs bool - OauthGitlab bool - GitlabDisplayName string - OauthGeneric bool - OauthGenericDisplayName string - OauthGitea bool - GiteaDisplayName string + *OAuthButtons + To string + Message template.HTML + Flashes []template.HTML + LoginUsername string }{ - pageForReq(app, r), - r.FormValue("to"), - template.HTML(""), - []template.HTML{}, - getTempInfo(app, "login-user", r, w), - app.Config().SlackOauth.ClientID != "", - app.Config().WriteAsOauth.ClientID != "", - app.Config().GitlabOauth.ClientID != "", - config.OrDefaultString(app.Config().GenericOauth.DisplayName, genericOauthDisplayName), - app.Config().GenericOauth.ClientID != "", - config.OrDefaultString(app.Config().GitlabOauth.DisplayName, gitlabDisplayName), - app.Config().GiteaOauth.ClientID != "", - config.OrDefaultString(app.Config().GiteaOauth.DisplayName, giteaDisplayName), + StaticPage: pageForReq(app, r), + OAuthButtons: NewOAuthButtons(app.Config()), + To: r.FormValue("to"), + Message: template.HTML(""), + Flashes: []template.HTML{}, + LoginUsername: getTempInfo(app, "login-user", r, w), } if earlyError != "" { diff --git a/activitypub.go b/activitypub.go index d34e70c..0e69075 100644 --- a/activitypub.go +++ b/activitypub.go @@ -494,7 +494,7 @@ func makeActivityPost(hostName string, p *activitystreams.Person, url string, m r, _ := http.NewRequest("POST", url, bytes.NewBuffer(b)) r.Header.Add("Content-Type", "application/activity+json") - r.Header.Set("User-Agent", "Go ("+serverSoftware+"/"+softwareVer+"; +"+hostName+")") + r.Header.Set("User-Agent", ServerUserAgent(hostName)) h := sha256.New() h.Write(b) r.Header.Add("Digest", "SHA-256="+base64.StdEncoding.EncodeToString(h.Sum(nil))) @@ -544,7 +544,7 @@ func resolveIRI(hostName, url string) ([]byte, error) { r, _ := http.NewRequest("GET", url, nil) r.Header.Add("Accept", "application/activity+json") - r.Header.Set("User-Agent", "Go ("+serverSoftware+"/"+softwareVer+"; +"+hostName+")") + r.Header.Set("User-Agent", ServerUserAgent(hostName)) if debugging { dump, err := httputil.DumpRequestOut(r, true) diff --git a/app.go b/app.go index af0a56f..2aed437 100644 --- a/app.go +++ b/app.go @@ -238,27 +238,16 @@ func handleViewLanding(app *App, w http.ResponseWriter, r *http.Request) error { p := struct { page.StaticPage + *OAuthButtons Flashes []template.HTML Banner template.HTML Content template.HTML ForcedLanding bool - - OauthSlack bool - OauthWriteAs bool - OauthGitlab bool - OauthGeneric bool - OauthGenericDisplayName string - GitlabDisplayName string }{ - StaticPage: pageForReq(app, r), - ForcedLanding: forceLanding, - OauthSlack: app.Config().SlackOauth.ClientID != "", - OauthWriteAs: app.Config().WriteAsOauth.ClientID != "", - OauthGitlab: app.Config().GitlabOauth.ClientID != "", - OauthGeneric: app.Config().GenericOauth.ClientID != "", - OauthGenericDisplayName: config.OrDefaultString(app.Config().GenericOauth.DisplayName, genericOauthDisplayName), - GitlabDisplayName: config.OrDefaultString(app.Config().GitlabOauth.DisplayName, gitlabDisplayName), + StaticPage: pageForReq(app, r), + OAuthButtons: NewOAuthButtons(app.Config()), + ForcedLanding: forceLanding, } banner, err := getLandingBanner(app) @@ -903,3 +892,13 @@ func adminInitDatabase(app *App) error { log.Info("Done.") return nil } + +// ServerUserAgent returns a User-Agent string to use in external requests. The +// hostName parameter may be left empty. +func ServerUserAgent(hostName string) string { + hostUAStr := "" + if hostName != "" { + hostUAStr = "; +" + hostName + } + return "Go (" + serverSoftware + "/" + softwareVer + hostUAStr + ")" +} diff --git a/config/config.go b/config/config.go index 9ff13f8..7b64e02 100644 --- a/config/config.go +++ b/config/config.go @@ -81,6 +81,15 @@ type ( CallbackProxyAPI string `ini:"callback_proxy_api"` } + GiteaOauthCfg struct { + ClientID string `ini:"client_id"` + ClientSecret string `ini:"client_secret"` + Host string `ini:"host"` + DisplayName string `ini:"display_name"` + CallbackProxy string `ini:"callback_proxy"` + CallbackProxyAPI string `ini:"callback_proxy_api"` + } + SlackOauthCfg struct { ClientID string `ini:"client_id"` ClientSecret string `ini:"client_secret"` @@ -101,14 +110,6 @@ type ( AuthEndpoint string `ini:"auth_endpoint"` AllowDisconnect bool `ini:"allow_disconnect"` } - GiteaOauthCfg struct { - ClientID string `ini:"client_id"` - ClientSecret string `ini:"client_secret"` - Host string `ini:"host"` - DisplayName string `ini:"display_name"` - CallbackProxy string `ini:"callback_proxy"` - CallbackProxyAPI string `ini:"callback_proxy_api"` - } // AppCfg holds values that affect how the application functions AppCfg struct { @@ -165,8 +166,8 @@ type ( SlackOauth SlackOauthCfg `ini:"oauth.slack"` WriteAsOauth WriteAsOauthCfg `ini:"oauth.writeas"` GitlabOauth GitlabOauthCfg `ini:"oauth.gitlab"` - GenericOauth GenericOauthCfg `ini:"oauth.generic"` GiteaOauth GiteaOauthCfg `ini:"oauth.gitea"` + GenericOauth GenericOauthCfg `ini:"oauth.generic"` } ) diff --git a/database.go b/database.go index c764340..8237e41 100644 --- a/database.go +++ b/database.go @@ -2627,11 +2627,11 @@ func (db *datastore) GetIDForRemoteUser(ctx context.Context, remoteUserID, provi } type oauthAccountInfo struct { - Provider string - ClientID string - RemoteUserID string - DisplayName string - AllowDisconnect bool + Provider string + ClientID string + RemoteUserID string + DisplayName string + AllowDisconnect bool } func (db *datastore) GetOauthAccounts(ctx context.Context, userID int64) ([]oauthAccountInfo, error) { diff --git a/invites.go b/invites.go index 10416b2..4e3eff4 100644 --- a/invites.go +++ b/invites.go @@ -170,14 +170,14 @@ func handleViewInvite(app *App, w http.ResponseWriter, r *http.Request) error { p := struct { page.StaticPage + *OAuthButtons Error string Flashes []template.HTML Invite string - OAuth *OAuthButtons }{ - StaticPage: pageForReq(app, r), - Invite: inviteCode, - OAuth: NewOAuthButtons(app.cfg), + StaticPage: pageForReq(app, r), + OAuthButtons: NewOAuthButtons(app.cfg), + Invite: inviteCode, } if expired { diff --git a/less/login.less b/less/login.less index 473d26f..fefeb12 100644 --- a/less/login.less +++ b/less/login.less @@ -9,18 +9,64 @@ */ .row.signinbtns { - justify-content: space-evenly; + justify-content: center; font-size: 1em; margin-top: 2em; margin-bottom: 1em; + flex-wrap: wrap; .loginbtn { height: 40px; - } + margin: 0.5em; + + &.btn { + box-sizing: border-box; + font-size: 17px; + white-space: nowrap; + + img { + height: 1.5em; + vertical-align: middle; + } + } + + &#writeas-login, &#slack-login { + img { + margin-top: -0.2em; + } + } + + &#gitlab-login { + background-color: #fc6d26; + border-color: #fc6d26; + &:hover { + background-color: darken(#fc6d26, 5%); + border-color: darken(#fc6d26, 5%); + } + } + + &#gitea-login { + background-color: #2ecc71; + border-color: #2ecc71; + &:hover { + background-color: #2cc26b; + border-color: #2cc26b; + } + } + + &#slack-login, &#gitlab-login, &#gitea-login, &#generic-oauth-login { + font-size: 0.86em; + font-family: @sansFont; + } - #writeas-login, #gitlab-login { - box-sizing: border-box; - font-size: 17px; + &#slack-login, &#generic-oauth-login { + color: @lightTextColor; + background-color: @lightNavBG; + border-color: @lightNavBorder; + &:hover { + background-color: @lightNavHoverBG; + } + } } } diff --git a/oauth.go b/oauth.go index fe9fe74..e3f65ef 100644 --- a/oauth.go +++ b/oauth.go @@ -30,19 +30,27 @@ import ( // OAuthButtons holds display information for different OAuth providers we support. type OAuthButtons struct { - SlackEnabled bool - WriteAsEnabled bool - GitLabEnabled bool - GitLabDisplayName string + SlackEnabled bool + WriteAsEnabled bool + GitLabEnabled bool + GitLabDisplayName string + GiteaEnabled bool + GiteaDisplayName string + GenericEnabled bool + GenericDisplayName string } // NewOAuthButtons creates a new OAuthButtons struct based on our app configuration. func NewOAuthButtons(cfg *config.Config) *OAuthButtons { return &OAuthButtons{ - SlackEnabled: cfg.SlackOauth.ClientID != "", - WriteAsEnabled: cfg.WriteAsOauth.ClientID != "", - GitLabEnabled: cfg.GitlabOauth.ClientID != "", - GitLabDisplayName: config.OrDefaultString(cfg.GitlabOauth.DisplayName, gitlabDisplayName), + SlackEnabled: cfg.SlackOauth.ClientID != "", + WriteAsEnabled: cfg.WriteAsOauth.ClientID != "", + GitLabEnabled: cfg.GitlabOauth.ClientID != "", + GitLabDisplayName: config.OrDefaultString(cfg.GitlabOauth.DisplayName, gitlabDisplayName), + GiteaEnabled: cfg.GiteaOauth.ClientID != "", + GiteaDisplayName: config.OrDefaultString(cfg.GiteaOauth.DisplayName, giteaDisplayName), + GenericEnabled: cfg.GenericOauth.ClientID != "", + GenericDisplayName: config.OrDefaultString(cfg.GenericOauth.DisplayName, genericOauthDisplayName), } } @@ -318,6 +326,12 @@ func (h oauthHandler) viewOauthCallback(app *App, w http.ResponseWriter, r *http tokenResponse, err := h.oauthClient.exchangeOauthCode(ctx, code) if err != nil { log.Error("Unable to exchangeOauthCode: %s", err) + // TODO: show user friendly message if needed + // TODO: show NO message for cases like user pressing "Cancel" on authorize step + addSessionFlash(app, w, r, err.Error(), nil) + if attachUserID > 0 { + return impart.HTTPError{http.StatusFound, "/me/settings"} + } return impart.HTTPError{http.StatusInternalServerError, err.Error()} } @@ -408,7 +422,7 @@ func (r *callbackProxyClient) register(ctx context.Context, state string) error if err != nil { return err } - req.Header.Set("User-Agent", "writefreely") + req.Header.Set("User-Agent", ServerUserAgent("")) req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/x-www-form-urlencoded") diff --git a/oauth_generic.go b/oauth_generic.go index 42c84b0..ce65bca 100644 --- a/oauth_generic.go +++ b/oauth_generic.go @@ -62,7 +62,7 @@ func (c genericOauthClient) exchangeOauthCode(ctx context.Context, code string) return nil, err } req.WithContext(ctx) - req.Header.Set("User-Agent", "writefreely") + 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) @@ -91,7 +91,7 @@ func (c genericOauthClient) inspectOauthAccessToken(ctx context.Context, accessT return nil, err } req.WithContext(ctx) - req.Header.Set("User-Agent", "writefreely") + req.Header.Set("User-Agent", ServerUserAgent("")) req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", "Bearer "+accessToken) diff --git a/oauth_gitea.go b/oauth_gitea.go index e6e1000..a9b7741 100644 --- a/oauth_gitea.go +++ b/oauth_gitea.go @@ -62,7 +62,7 @@ func (c giteaOauthClient) exchangeOauthCode(ctx context.Context, code string) (* return nil, err } req.WithContext(ctx) - req.Header.Set("User-Agent", "writefreely") + 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) @@ -91,7 +91,7 @@ func (c giteaOauthClient) inspectOauthAccessToken(ctx context.Context, accessTok return nil, err } req.WithContext(ctx) - req.Header.Set("User-Agent", "writefreely") + req.Header.Set("User-Agent", ServerUserAgent("")) req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", "Bearer "+accessToken) diff --git a/oauth_gitlab.go b/oauth_gitlab.go index c9c74aa..ad919e4 100644 --- a/oauth_gitlab.go +++ b/oauth_gitlab.go @@ -63,7 +63,7 @@ func (c gitlabOauthClient) exchangeOauthCode(ctx context.Context, code string) ( return nil, err } req.WithContext(ctx) - req.Header.Set("User-Agent", "writefreely") + 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) @@ -92,7 +92,7 @@ func (c gitlabOauthClient) inspectOauthAccessToken(ctx context.Context, accessTo return nil, err } req.WithContext(ctx) - req.Header.Set("User-Agent", "writefreely") + req.Header.Set("User-Agent", ServerUserAgent("")) req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", "Bearer "+accessToken) diff --git a/oauth_slack.go b/oauth_slack.go index c881ab6..bad3775 100644 --- a/oauth_slack.go +++ b/oauth_slack.go @@ -111,7 +111,7 @@ func (c slackOauthClient) exchangeOauthCode(ctx context.Context, code string) (* return nil, err } req.WithContext(ctx) - req.Header.Set("User-Agent", "writefreely") + 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) @@ -140,7 +140,7 @@ func (c slackOauthClient) inspectOauthAccessToken(ctx context.Context, accessTok return nil, err } req.WithContext(ctx) - req.Header.Set("User-Agent", "writefreely") + req.Header.Set("User-Agent", ServerUserAgent("")) req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", "Bearer "+accessToken) diff --git a/oauth_writeas.go b/oauth_writeas.go index 6251a16..e58f6e9 100644 --- a/oauth_writeas.go +++ b/oauth_writeas.go @@ -62,7 +62,7 @@ func (c writeAsOauthClient) exchangeOauthCode(ctx context.Context, code string) return nil, err } req.WithContext(ctx) - req.Header.Set("User-Agent", "writefreely") + 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) @@ -91,7 +91,7 @@ func (c writeAsOauthClient) inspectOauthAccessToken(ctx context.Context, accessT return nil, err } req.WithContext(ctx) - req.Header.Set("User-Agent", "writefreely") + req.Header.Set("User-Agent", ServerUserAgent("")) req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", "Bearer "+accessToken) diff --git a/pages/landing.tmpl b/pages/landing.tmpl index f968404..2131b40 100644 --- a/pages/landing.tmpl +++ b/pages/landing.tmpl @@ -60,10 +60,8 @@ form dd { margin-top: 0; max-width: 8em; } -#generic-oauth-login { - box-sizing: border-box; - font-size: 17px; - white-space:nowrap; +.or { + margin-bottom: 2.5em !important; } {{end}} @@ -78,20 +76,7 @@ form dd { {{ if .OpenRegistration }} - {{ if or .OauthSlack .OauthWriteAs .OauthGitlab .OauthGeneric }} - {{ if .OauthSlack }} -
Sign in with Slack
- {{ end }} - {{ if .OauthWriteAs }} -
Sign in with Write.as
- {{ end }} - {{ if .OauthGitlab }} -
Sign in with {{.GitlabDisplayName}}
- {{ end }} - {{ if .OauthGeneric }} -
Sign in with {{ .OauthGenericDisplayName }}
- {{ end }} - {{ end }} + {{template "oauth-buttons" .}} {{if not .DisablePasswordAuth}} {{if .Flashes}}{{end}} - {{ if or .OauthSlack .OauthWriteAs .OauthGitlab .OauthGeneric .OauthGitea }} -
- {{ if .OauthSlack }} - Sign in with Slack - {{ end }} - {{ if .OauthWriteAs }} - Sign in with Write.as - {{ end }} - {{ if .OauthGitlab }} - Sign in with {{.GitlabDisplayName}} - {{ end }} - {{ if .OauthGeneric }} - Sign in with {{ .OauthGenericDisplayName }} - {{ end }} - {{ if .OauthGitea }} - Sign in with {{.GiteaDisplayName}} - {{ end }} -
- - {{if not .DisablePasswordAuth}} -
-

or

-
-
- {{end}} - {{ end }} + {{template "oauth-buttons" .}} {{if not .DisablePasswordAuth}}
diff --git a/pages/signup.tmpl b/pages/signup.tmpl index c17aee3..b1bb50d 100644 --- a/pages/signup.tmpl +++ b/pages/signup.tmpl @@ -70,25 +70,9 @@ form dd { {{end}}
- {{ if or .OAuth.SlackEnabled .OAuth.WriteAsEnabled .OAuth.GitLabEnabled }} -
- {{ if .OAuth.SlackEnabled }} - Sign in with Slack - {{ end }} - {{ if .OAuth.WriteAsEnabled }} - Sign in with Write.as - {{ end }} - {{ if .OAuth.GitLabEnabled }} - Sign in with {{.OAuth.GitLabDisplayName}} - {{ end }} -
- -
-

or

-
-
- {{ end }} + {{template "oauth-buttons" .}} + {{if not .DisablePasswordAuth}}
@@ -112,6 +96,7 @@ form dd {
+ {{end}}
{{ end }} diff --git a/static/img/mark/writeas-white.png b/static/img/mark/writeas-white.png new file mode 100644 index 0000000..6c9b2cd Binary files /dev/null and b/static/img/mark/writeas-white.png differ diff --git a/templates.go b/templates.go index 5ee4bcf..be1412c 100644 --- a/templates.go +++ b/templates.go @@ -85,12 +85,18 @@ func initPage(parentDir, path, key string) { log.Info(" [%s] %s", key, path) } - pages[key] = template.Must(template.New("").Funcs(funcMap).ParseFiles( + files := []string{ path, filepath.Join(parentDir, templatesDir, "include", "footer.tmpl"), filepath.Join(parentDir, templatesDir, "base.tmpl"), filepath.Join(parentDir, templatesDir, "user", "include", "silenced.tmpl"), - )) + } + + if key == "login.tmpl" || key == "landing.tmpl" || key == "signup.tmpl" { + files = append(files, filepath.Join(parentDir, templatesDir, "include", "oauth.tmpl")) + } + + pages[key] = template.Must(template.New("").Funcs(funcMap).ParseFiles(files...)) } func initUserPage(parentDir, path, key string) { diff --git a/templates/include/oauth.tmpl b/templates/include/oauth.tmpl new file mode 100644 index 0000000..9a8d05e --- /dev/null +++ b/templates/include/oauth.tmpl @@ -0,0 +1,37 @@ +{{define "oauth-buttons"}} + {{ if or .SlackEnabled .WriteAsEnabled .GitLabEnabled .GiteaEnabled .GenericEnabled }} +
+ {{ if .SlackEnabled }} + Sign in with Slack + {{ end }} + {{ if .WriteAsEnabled }} + + + Sign in with Write.as + + {{ end }} + {{ if .GitLabEnabled }} + + + Sign in with {{.GitLabDisplayName}} + + {{ end }} + {{ if .GiteaEnabled }} + + + Sign in with {{.GiteaDisplayName}} + + {{ end }} + {{ if .GenericEnabled }} + Sign in with {{.GenericDisplayName}} + {{ end }} +
+ + {{if not .DisablePasswordAuth}} +
+

or

+
+
+ {{end}} + {{ end }} +{{end}} \ No newline at end of file diff --git a/templates/user/settings.tmpl b/templates/user/settings.tmpl index b6abadc..22de3d8 100644 --- a/templates/user/settings.tmpl +++ b/templates/user/settings.tmpl @@ -75,18 +75,18 @@ h3 { font-weight: normal; } {{end}} - {{ if .OauthSection }} + {{ if .OauthSection }}
- {{ if .OauthAccounts }} + {{ if .OauthAccounts }}

Linked Accounts

These are your linked external accounts.

- {{ range $oauth_account := .OauthAccounts }} -
- - - + {{ range $oauth_account := .OauthAccounts }} + + + +
{{ if $oauth_account.DisplayName}} {{ if $oauth_account.AllowDisconnect}} @@ -99,58 +99,58 @@ h3 { font-weight: normal; } {{end}}
-
- {{ end }} + + {{ end }}
{{ end }} {{ if or .OauthSlack .OauthWriteAs .OauthGitLab .OauthGeneric .OauthGitea }}

Link External Accounts

Connect additional accounts to enable logging in with those providers, instead of using your username and password.

-
+
{{ if .OauthWriteAs }} {{ end }} - {{ if .OauthSlack }} + {{ if .OauthSlack }} - {{ end }} - {{ if .OauthGitLab }} + {{ end }} + {{ if .OauthGitLab }} - {{ end }} - {{ if .OauthGitea }} + {{ end }} + {{ if .OauthGitea }} - {{ end }} -
- {{ if .OauthGeneric }} -
- + {{ end }} + {{ if .OauthGeneric }} + - {{ end }} + {{ end }} +
{{ end }} - {{ end }} + {{ end }}