Login with generic oauth feature++pull/376/head
@@ -86,6 +86,11 @@ func apiSignup(app *App, w http.ResponseWriter, r *http.Request) error { | |||||
} | } | ||||
func signup(app *App, w http.ResponseWriter, r *http.Request) (*AuthUser, error) { | func signup(app *App, w http.ResponseWriter, r *http.Request) (*AuthUser, error) { | ||||
if app.cfg.App.DisablePasswordAuth { | |||||
err := ErrDisabledPasswordAuth | |||||
return nil, err | |||||
} | |||||
reqJSON := IsJSON(r) | reqJSON := IsJSON(r) | ||||
// Get params | // Get params | ||||
@@ -299,16 +304,18 @@ func viewLogin(app *App, w http.ResponseWriter, r *http.Request) error { | |||||
p := &struct { | p := &struct { | ||||
page.StaticPage | page.StaticPage | ||||
To string | |||||
Message template.HTML | |||||
Flashes []template.HTML | |||||
LoginUsername string | |||||
OauthSlack bool | |||||
OauthWriteAs bool | |||||
OauthGitlab bool | |||||
GitlabDisplayName string | |||||
OauthGitea bool | |||||
GiteaDisplayName string | |||||
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 | |||||
}{ | }{ | ||||
pageForReq(app, r), | pageForReq(app, r), | ||||
r.FormValue("to"), | r.FormValue("to"), | ||||
@@ -318,6 +325,8 @@ func viewLogin(app *App, w http.ResponseWriter, r *http.Request) error { | |||||
app.Config().SlackOauth.ClientID != "", | app.Config().SlackOauth.ClientID != "", | ||||
app.Config().WriteAsOauth.ClientID != "", | app.Config().WriteAsOauth.ClientID != "", | ||||
app.Config().GitlabOauth.ClientID != "", | app.Config().GitlabOauth.ClientID != "", | ||||
config.OrDefaultString(app.Config().GenericOauth.DisplayName, genericOauthDisplayName), | |||||
app.Config().GenericOauth.ClientID != "", | |||||
config.OrDefaultString(app.Config().GitlabOauth.DisplayName, gitlabDisplayName), | config.OrDefaultString(app.Config().GitlabOauth.DisplayName, gitlabDisplayName), | ||||
app.Config().GiteaOauth.ClientID != "", | app.Config().GiteaOauth.ClientID != "", | ||||
config.OrDefaultString(app.Config().GiteaOauth.DisplayName, giteaDisplayName), | config.OrDefaultString(app.Config().GiteaOauth.DisplayName, giteaDisplayName), | ||||
@@ -395,6 +404,11 @@ func login(app *App, w http.ResponseWriter, r *http.Request) error { | |||||
var err error | var err error | ||||
var signin userCredentials | var signin userCredentials | ||||
if app.cfg.App.DisablePasswordAuth { | |||||
err := ErrDisabledPasswordAuth | |||||
return err | |||||
} | |||||
// Log in with one-time token if one is given | // Log in with one-time token if one is given | ||||
if oneTimeToken != "" { | if oneTimeToken != "" { | ||||
log.Info("Login: Logging user in via token.") | log.Info("Login: Logging user in via token.") | ||||
@@ -1049,6 +1063,7 @@ func viewSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) err | |||||
enableOauthSlack := app.Config().SlackOauth.ClientID != "" | enableOauthSlack := app.Config().SlackOauth.ClientID != "" | ||||
enableOauthWriteAs := app.Config().WriteAsOauth.ClientID != "" | enableOauthWriteAs := app.Config().WriteAsOauth.ClientID != "" | ||||
enableOauthGitLab := app.Config().GitlabOauth.ClientID != "" | enableOauthGitLab := app.Config().GitlabOauth.ClientID != "" | ||||
enableOauthGeneric := app.Config().GenericOauth.ClientID != "" | |||||
enableOauthGitea := app.Config().GiteaOauth.ClientID != "" | enableOauthGitea := app.Config().GiteaOauth.ClientID != "" | ||||
oauthAccounts, err := app.db.GetOauthAccounts(r.Context(), u.ID) | oauthAccounts, err := app.db.GetOauthAccounts(r.Context(), u.ID) | ||||
@@ -1056,7 +1071,7 @@ func viewSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) err | |||||
log.Error("Unable to get oauth accounts for settings: %s", err) | log.Error("Unable to get oauth accounts for settings: %s", err) | ||||
return impart.HTTPError{http.StatusInternalServerError, "Unable to retrieve user data. The humans have been alerted."} | return impart.HTTPError{http.StatusInternalServerError, "Unable to retrieve user data. The humans have been alerted."} | ||||
} | } | ||||
for _, oauthAccount := range oauthAccounts { | |||||
for idx, oauthAccount := range oauthAccounts { | |||||
switch oauthAccount.Provider { | switch oauthAccount.Provider { | ||||
case "slack": | case "slack": | ||||
enableOauthSlack = false | enableOauthSlack = false | ||||
@@ -1064,41 +1079,49 @@ func viewSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) err | |||||
enableOauthWriteAs = false | enableOauthWriteAs = false | ||||
case "gitlab": | case "gitlab": | ||||
enableOauthGitLab = false | enableOauthGitLab = false | ||||
case "generic": | |||||
oauthAccounts[idx].DisplayName = app.Config().GenericOauth.DisplayName | |||||
oauthAccounts[idx].AllowDisconnect = app.Config().GenericOauth.AllowDisconnect | |||||
enableOauthGeneric = false | |||||
case "gitea": | case "gitea": | ||||
enableOauthGitea = false | enableOauthGitea = false | ||||
} | } | ||||
} | } | ||||
displayOauthSection := enableOauthSlack || enableOauthWriteAs || enableOauthGitLab || enableOauthGitea || len(oauthAccounts) > 0 | |||||
displayOauthSection := enableOauthSlack || enableOauthWriteAs || enableOauthGitLab || enableOauthGeneric || enableOauthGitea || len(oauthAccounts) > 0 | |||||
obj := struct { | obj := struct { | ||||
*UserPage | *UserPage | ||||
Email string | |||||
HasPass bool | |||||
IsLogOut bool | |||||
Silenced bool | |||||
OauthSection bool | |||||
OauthAccounts []oauthAccountInfo | |||||
OauthSlack bool | |||||
OauthWriteAs bool | |||||
OauthGitLab bool | |||||
GitLabDisplayName string | |||||
OauthGitea bool | |||||
GiteaDisplayName string | |||||
Email string | |||||
HasPass bool | |||||
IsLogOut bool | |||||
Silenced bool | |||||
OauthSection bool | |||||
OauthAccounts []oauthAccountInfo | |||||
OauthSlack bool | |||||
OauthWriteAs bool | |||||
OauthGitLab bool | |||||
GitLabDisplayName string | |||||
OauthGeneric bool | |||||
OauthGenericDisplayName string | |||||
OauthGitea bool | |||||
GiteaDisplayName string | |||||
}{ | }{ | ||||
UserPage: NewUserPage(app, r, u, "Account Settings", flashes), | |||||
Email: fullUser.EmailClear(app.keys), | |||||
HasPass: passIsSet, | |||||
IsLogOut: r.FormValue("logout") == "1", | |||||
Silenced: fullUser.IsSilenced(), | |||||
OauthSection: displayOauthSection, | |||||
OauthAccounts: oauthAccounts, | |||||
OauthSlack: enableOauthSlack, | |||||
OauthWriteAs: enableOauthWriteAs, | |||||
OauthGitLab: enableOauthGitLab, | |||||
GitLabDisplayName: config.OrDefaultString(app.Config().GitlabOauth.DisplayName, gitlabDisplayName), | |||||
OauthGitea: enableOauthGitea, | |||||
GiteaDisplayName: config.OrDefaultString(app.Config().GiteaOauth.DisplayName, giteaDisplayName), | |||||
UserPage: NewUserPage(app, r, u, "Account Settings", flashes), | |||||
Email: fullUser.EmailClear(app.keys), | |||||
HasPass: passIsSet, | |||||
IsLogOut: r.FormValue("logout") == "1", | |||||
Silenced: fullUser.IsSilenced(), | |||||
OauthSection: displayOauthSection, | |||||
OauthAccounts: oauthAccounts, | |||||
OauthSlack: enableOauthSlack, | |||||
OauthWriteAs: enableOauthWriteAs, | |||||
OauthGitLab: enableOauthGitLab, | |||||
GitLabDisplayName: config.OrDefaultString(app.Config().GitlabOauth.DisplayName, gitlabDisplayName), | |||||
OauthGeneric: enableOauthGeneric, | |||||
OauthGenericDisplayName: config.OrDefaultString(app.Config().GenericOauth.DisplayName, genericOauthDisplayName), | |||||
OauthGitea: enableOauthGitea, | |||||
GiteaDisplayName: config.OrDefaultString(app.Config().GiteaOauth.DisplayName, giteaDisplayName), | |||||
} | } | ||||
showUserPage(w, "settings", obj) | showUserPage(w, "settings", obj) | ||||
@@ -243,9 +243,22 @@ func handleViewLanding(app *App, w http.ResponseWriter, r *http.Request) error { | |||||
Content template.HTML | Content template.HTML | ||||
ForcedLanding bool | ForcedLanding bool | ||||
OauthSlack bool | |||||
OauthWriteAs bool | |||||
OauthGitlab bool | |||||
OauthGeneric bool | |||||
OauthGenericDisplayName string | |||||
GitlabDisplayName string | |||||
}{ | }{ | ||||
StaticPage: pageForReq(app, r), | |||||
ForcedLanding: forceLanding, | |||||
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), | |||||
} | } | ||||
banner, err := getLandingBanner(app) | banner, err := getLandingBanner(app) | ||||
@@ -89,6 +89,18 @@ type ( | |||||
CallbackProxyAPI string `ini:"callback_proxy_api"` | CallbackProxyAPI string `ini:"callback_proxy_api"` | ||||
} | } | ||||
GenericOauthCfg 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"` | |||||
TokenEndpoint string `ini:"token_endpoint"` | |||||
InspectEndpoint string `ini:"inspect_endpoint"` | |||||
AuthEndpoint string `ini:"auth_endpoint"` | |||||
AllowDisconnect bool `ini:"allow_disconnect"` | |||||
} | |||||
GiteaOauthCfg struct { | GiteaOauthCfg struct { | ||||
ClientID string `ini:"client_id"` | ClientID string `ini:"client_id"` | ||||
ClientSecret string `ini:"client_secret"` | ClientSecret string `ini:"client_secret"` | ||||
@@ -140,6 +152,9 @@ type ( | |||||
// Check for Updates | // Check for Updates | ||||
UpdateChecks bool `ini:"update_checks"` | UpdateChecks bool `ini:"update_checks"` | ||||
// Disable password authentication if use only Oauth | |||||
DisablePasswordAuth bool `ini:"disable_password_auth"` | |||||
} | } | ||||
// Config holds the complete configuration for running a writefreely instance | // Config holds the complete configuration for running a writefreely instance | ||||
@@ -150,6 +165,7 @@ type ( | |||||
SlackOauth SlackOauthCfg `ini:"oauth.slack"` | SlackOauth SlackOauthCfg `ini:"oauth.slack"` | ||||
WriteAsOauth WriteAsOauthCfg `ini:"oauth.writeas"` | WriteAsOauth WriteAsOauthCfg `ini:"oauth.writeas"` | ||||
GitlabOauth GitlabOauthCfg `ini:"oauth.gitlab"` | GitlabOauth GitlabOauthCfg `ini:"oauth.gitlab"` | ||||
GenericOauth GenericOauthCfg `ini:"oauth.generic"` | |||||
GiteaOauth GiteaOauthCfg `ini:"oauth.gitea"` | GiteaOauth GiteaOauthCfg `ini:"oauth.gitea"` | ||||
} | } | ||||
) | ) | ||||
@@ -2627,9 +2627,11 @@ func (db *datastore) GetIDForRemoteUser(ctx context.Context, remoteUserID, provi | |||||
} | } | ||||
type oauthAccountInfo struct { | type oauthAccountInfo struct { | ||||
Provider string | |||||
ClientID string | |||||
RemoteUserID string | |||||
Provider string | |||||
ClientID string | |||||
RemoteUserID string | |||||
DisplayName string | |||||
AllowDisconnect bool | |||||
} | } | ||||
func (db *datastore) GetOauthAccounts(ctx context.Context, userID int64) ([]oauthAccountInfo, error) { | func (db *datastore) GetOauthAccounts(ctx context.Context, userID int64) ([]oauthAccountInfo, error) { | ||||
@@ -52,6 +52,8 @@ var ( | |||||
ErrUserNotFoundEmail = impart.HTTPError{http.StatusNotFound, "Please enter your username instead of your email address."} | ErrUserNotFoundEmail = impart.HTTPError{http.StatusNotFound, "Please enter your username instead of your email address."} | ||||
ErrUserSilenced = impart.HTTPError{http.StatusForbidden, "Account is silenced."} | ErrUserSilenced = impart.HTTPError{http.StatusForbidden, "Account is silenced."} | ||||
ErrDisabledPasswordAuth = impart.HTTPError{http.StatusForbidden, "Password authentication is disabled."} | |||||
) | ) | ||||
// Post operation errors | // Post operation errors | ||||
@@ -235,6 +235,33 @@ func configureGitlabOauth(parentHandler *Handler, r *mux.Router, app *App) { | |||||
} | } | ||||
} | } | ||||
func configureGenericOauth(parentHandler *Handler, r *mux.Router, app *App) { | |||||
if app.Config().GenericOauth.ClientID != "" { | |||||
callbackLocation := app.Config().App.Host + "/oauth/callback/generic" | |||||
var callbackProxy *callbackProxyClient = nil | |||||
if app.Config().GenericOauth.CallbackProxy != "" { | |||||
callbackProxy = &callbackProxyClient{ | |||||
server: app.Config().GenericOauth.CallbackProxyAPI, | |||||
callbackLocation: app.Config().App.Host + "/oauth/callback/generic", | |||||
httpClient: config.DefaultHTTPClient(), | |||||
} | |||||
callbackLocation = app.Config().GenericOauth.CallbackProxy | |||||
} | |||||
oauthClient := genericOauthClient{ | |||||
ClientID: app.Config().GenericOauth.ClientID, | |||||
ClientSecret: app.Config().GenericOauth.ClientSecret, | |||||
ExchangeLocation: app.Config().GenericOauth.Host + app.Config().GenericOauth.TokenEndpoint, | |||||
InspectLocation: app.Config().GenericOauth.Host + app.Config().GenericOauth.InspectEndpoint, | |||||
AuthLocation: app.Config().GenericOauth.Host + app.Config().GenericOauth.AuthEndpoint, | |||||
HttpClient: config.DefaultHTTPClient(), | |||||
CallbackLocation: callbackLocation, | |||||
} | |||||
configureOauthRoutes(parentHandler, r, app, oauthClient, callbackProxy) | |||||
} | |||||
} | |||||
func configureGiteaOauth(parentHandler *Handler, r *mux.Router, app *App) { | func configureGiteaOauth(parentHandler *Handler, r *mux.Router, app *App) { | ||||
if app.Config().GiteaOauth.ClientID != "" { | if app.Config().GiteaOauth.ClientID != "" { | ||||
callbackLocation := app.Config().App.Host + "/oauth/callback/gitea" | callbackLocation := app.Config().App.Host + "/oauth/callback/gitea" | ||||
@@ -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", "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 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", "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 | |||||
} |
@@ -60,6 +60,11 @@ form dd { | |||||
margin-top: 0; | margin-top: 0; | ||||
max-width: 8em; | max-width: 8em; | ||||
} | } | ||||
#generic-oauth-login { | |||||
box-sizing: border-box; | |||||
font-size: 17px; | |||||
white-space:nowrap; | |||||
} | |||||
</style> | </style> | ||||
{{end}} | {{end}} | ||||
{{define "content"}} | {{define "content"}} | ||||
@@ -73,6 +78,21 @@ form dd { | |||||
<div{{if not .OpenRegistration}} style="padding: 2em 0;"{{end}}> | <div{{if not .OpenRegistration}} style="padding: 2em 0;"{{end}}> | ||||
{{ if .OpenRegistration }} | {{ if .OpenRegistration }} | ||||
{{ if or .OauthSlack .OauthWriteAs .OauthGitlab .OauthGeneric }} | |||||
{{ if .OauthSlack }} | |||||
<div class="row content-container signinbtns signinoauthbtns"><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></div> | |||||
{{ end }} | |||||
{{ if .OauthWriteAs }} | |||||
<div class="row content-container signinbtns signinoauthbtns"><a class="btn cta loginbtn" id="writeas-login" href="/oauth/write.as">Sign in with <strong>Write.as</strong></a></div> | |||||
{{ end }} | |||||
{{ if .OauthGitlab }} | |||||
<div class="row content-container signinbtns signinoauthbtns"><a class="btn cta loginbtn" id="gitlab-login" href="/oauth/gitlab">Sign in with <strong>{{.GitlabDisplayName}}</strong></a></div> | |||||
{{ end }} | |||||
{{ if .OauthGeneric }} | |||||
<div class="row content-container signinbtns signinoauthbtns"><a class="btn cta loginbtn" id="generic-oauth-login" href="/oauth/generic">Sign in with <strong>{{ .OauthGenericDisplayName }}</strong></a></div> | |||||
{{ end }} | |||||
{{ end }} | |||||
{{if not .DisablePasswordAuth}} | |||||
{{if .Flashes}}<ul class="errors"> | {{if .Flashes}}<ul class="errors"> | ||||
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}} | {{range .Flashes}}<li class="urgent">{{.}}</li>{{end}} | ||||
</ul>{{end}} | </ul>{{end}} | ||||
@@ -101,6 +121,7 @@ form dd { | |||||
</dl> | </dl> | ||||
</form> | </form> | ||||
</div> | </div> | ||||
{{end}} | |||||
{{ else }} | {{ else }} | ||||
<p style="font-size: 1.3em; margin: 1rem 0;">Registration is currently closed.</p> | <p style="font-size: 1.3em; margin: 1rem 0;">Registration is currently closed.</p> | ||||
<p>You can always sign up on <a href="https://writefreely.org/instances">another instance</a>.</p> | <p>You can always sign up on <a href="https://writefreely.org/instances">another instance</a>.</p> | ||||
@@ -3,6 +3,10 @@ | |||||
<meta itemprop="description" content="Log in to {{.SiteName}}."> | <meta itemprop="description" content="Log in to {{.SiteName}}."> | ||||
<style> | <style> | ||||
input{margin-bottom:0.5em;} | input{margin-bottom:0.5em;} | ||||
#generic-oauth-login { | |||||
box-sizing: border-box; | |||||
font-size: 17px; | |||||
} | |||||
</style> | </style> | ||||
{{end}} | {{end}} | ||||
{{define "content"}} | {{define "content"}} | ||||
@@ -13,7 +17,7 @@ input{margin-bottom:0.5em;} | |||||
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}} | {{range .Flashes}}<li class="urgent">{{.}}</li>{{end}} | ||||
</ul>{{end}} | </ul>{{end}} | ||||
{{ if or .OauthSlack .OauthWriteAs .OauthGitlab .OauthGitea }} | |||||
{{ if or .OauthSlack .OauthWriteAs .OauthGitlab .OauthGeneric .OauthGitea }} | |||||
<div class="row content-container signinbtns"> | <div class="row content-container signinbtns"> | ||||
{{ if .OauthSlack }} | {{ 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> | <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> | ||||
@@ -24,17 +28,23 @@ input{margin-bottom:0.5em;} | |||||
{{ if .OauthGitlab }} | {{ if .OauthGitlab }} | ||||
<a class="btn cta loginbtn" id="gitlab-login" href="/oauth/gitlab">Sign in with <strong>{{.GitlabDisplayName}}</strong></a> | <a class="btn cta loginbtn" id="gitlab-login" href="/oauth/gitlab">Sign in with <strong>{{.GitlabDisplayName}}</strong></a> | ||||
{{ end }} | {{ end }} | ||||
{{ if .OauthGeneric }} | |||||
<a class="btn cta loginbtn" id="generic-oauth-login" href="/oauth/generic">Sign in with <strong>{{ .OauthGenericDisplayName }}</strong></a> | |||||
{{ end }} | |||||
{{ if .OauthGitea }} | {{ if .OauthGitea }} | ||||
<a class="btn cta loginbtn" id="gitea-login" href="/oauth/gitea">Sign in with <strong>{{.GiteaDisplayName}}</strong></a> | <a class="btn cta loginbtn" id="gitea-login" href="/oauth/gitea">Sign in with <strong>{{.GiteaDisplayName}}</strong></a> | ||||
{{ end }} | {{ end }} | ||||
</div> | </div> | ||||
<div class="or"> | |||||
<p>or</p> | |||||
<hr class="short" /> | |||||
</div> | |||||
{{if not .DisablePasswordAuth}} | |||||
<div class="or"> | |||||
<p>or</p> | |||||
<hr class="short" /> | |||||
</div> | |||||
{{end}} | |||||
{{ end }} | {{ end }} | ||||
{{if not .DisablePasswordAuth}} | |||||
<form action="/auth/login" method="post" style="text-align: center;margin-top:1em;" onsubmit="disableSubmit()"> | <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="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 /> | <input type="password" name="pass" placeholder="Password" {{if .LoginUsername}}autofocus{{end}} /><br /> | ||||
@@ -44,11 +54,12 @@ input{margin-bottom:0.5em;} | |||||
{{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}} | {{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> | |||||
<script type="text/javascript"> | |||||
function disableSubmit() { | |||||
var $btn = document.getElementById("btn-login"); | |||||
$btn.value = "Logging in..."; | |||||
$btn.disabled = true; | |||||
} | |||||
</script> | |||||
{{end}} | |||||
{{end}} | {{end}} |
@@ -76,6 +76,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { | |||||
configureSlackOauth(handler, write, apper.App()) | configureSlackOauth(handler, write, apper.App()) | ||||
configureWriteAsOauth(handler, write, apper.App()) | configureWriteAsOauth(handler, write, apper.App()) | ||||
configureGitlabOauth(handler, write, apper.App()) | configureGitlabOauth(handler, write, apper.App()) | ||||
configureGenericOauth(handler, write, apper.App()) | |||||
configureGiteaOauth(handler, write, apper.App()) | configureGiteaOauth(handler, write, apper.App()) | ||||
// Set up dyamic page handlers | // Set up dyamic page handlers | ||||
@@ -41,6 +41,7 @@ h3 { font-weight: normal; } | |||||
</form> | </form> | ||||
{{ end }} | {{ end }} | ||||
{{if not .DisablePasswordAuth}} | |||||
<form method="post" action="/api/me/self" autocomplete="false"> | <form method="post" action="/api/me/self" autocomplete="false"> | ||||
<input type="hidden" name="logout" value="{{.IsLogOut}}" /> | <input type="hidden" name="logout" value="{{.IsLogOut}}" /> | ||||
<div class="option"> | <div class="option"> | ||||
@@ -72,6 +73,7 @@ h3 { font-weight: normal; } | |||||
<input type="submit" value="Save changes" tabindex="4" /> | <input type="submit" value="Save changes" tabindex="4" /> | ||||
</div> | </div> | ||||
</form> | </form> | ||||
{{end}} | |||||
{{ if .OauthSection }} | {{ if .OauthSection }} | ||||
<hr /> | <hr /> | ||||
@@ -86,14 +88,22 @@ h3 { font-weight: normal; } | |||||
<input type="hidden" name="client_id" value="{{ $oauth_account.ClientID }}" /> | <input type="hidden" name="client_id" value="{{ $oauth_account.ClientID }}" /> | ||||
<input type="hidden" name="remote_user_id" value="{{ $oauth_account.RemoteUserID }}" /> | <input type="hidden" name="remote_user_id" value="{{ $oauth_account.RemoteUserID }}" /> | ||||
<div class="section oauth-provider"> | <div class="section oauth-provider"> | ||||
<img src="/img/mark/{{$oauth_account.Provider}}.png" alt="{{ $oauth_account.Provider | title }}" /> | |||||
<input type="submit" value="Remove {{ $oauth_account.Provider | title }}" /> | |||||
{{ if $oauth_account.DisplayName}} | |||||
{{ if $oauth_account.AllowDisconnect}} | |||||
<input type="submit" value="Remove {{.DisplayName}}" /> | |||||
{{else}} | |||||
<a class="btn cta"><strong>{{.DisplayName}}</strong></a> | |||||
{{end}} | |||||
{{else}} | |||||
<img src="/img/mark/{{$oauth_account.Provider}}.png" alt="{{ $oauth_account.Provider | title }}" /> | |||||
<input type="submit" value="Remove {{ $oauth_account.Provider | title }}" /> | |||||
{{end}} | |||||
</div> | </div> | ||||
</form> | </form> | ||||
{{ end }} | {{ end }} | ||||
</div> | </div> | ||||
{{ end }} | {{ end }} | ||||
{{ if or .OauthSlack .OauthWriteAs .OauthGitLab .OauthGitea }} | |||||
{{ if or .OauthSlack .OauthWriteAs .OauthGitLab .OauthGeneric .OauthGitea }} | |||||
<div class="option"> | <div class="option"> | ||||
<h2>Link External Accounts</h2> | <h2>Link External Accounts</h2> | ||||
<p>Connect additional accounts to enable logging in with those providers, instead of using your username and password.</p> | <p>Connect additional accounts to enable logging in with those providers, instead of using your username and password.</p> | ||||
@@ -131,6 +141,13 @@ h3 { font-weight: normal; } | |||||
</div> | </div> | ||||
{{ end }} | {{ end }} | ||||
</div> | </div> | ||||
{{ if .OauthGeneric }} | |||||
<div class="row"> | |||||
<div class="section oauth-provider"> | |||||
<p><a class="btn cta loginbtn" id="generic-oauth-login" href="/oauth/generic?attach=t">Link <strong>{{ .OauthGenericDisplayName }}</strong></a></p> | |||||
</div> | |||||
</div> | |||||
{{ end }} | |||||
</div> | </div> | ||||
{{ end }} | {{ end }} | ||||
{{ end }} | {{ end }} | ||||