Added Gitea OAuth login and account management.
This commit is contained in:
parent
d6cb178eb6
commit
c798a44f69
13
account.go
13
account.go
|
@ -311,6 +311,8 @@ func viewLogin(app *App, w http.ResponseWriter, r *http.Request) error {
|
||||||
OauthWriteAs bool
|
OauthWriteAs bool
|
||||||
OauthGitlab bool
|
OauthGitlab bool
|
||||||
GitlabDisplayName string
|
GitlabDisplayName string
|
||||||
|
OauthGitea bool
|
||||||
|
GiteaDisplayName string
|
||||||
}{
|
}{
|
||||||
pageForReq(app, r),
|
pageForReq(app, r),
|
||||||
r.FormValue("to"),
|
r.FormValue("to"),
|
||||||
|
@ -321,6 +323,8 @@ func viewLogin(app *App, w http.ResponseWriter, r *http.Request) error {
|
||||||
app.Config().WriteAsOauth.ClientID != "",
|
app.Config().WriteAsOauth.ClientID != "",
|
||||||
app.Config().GitlabOauth.ClientID != "",
|
app.Config().GitlabOauth.ClientID != "",
|
||||||
config.OrDefaultString(app.Config().GitlabOauth.DisplayName, gitlabDisplayName),
|
config.OrDefaultString(app.Config().GitlabOauth.DisplayName, gitlabDisplayName),
|
||||||
|
app.Config().GiteaOauth.ClientID != "",
|
||||||
|
config.OrDefaultString(app.Config().GiteaOauth.DisplayName, giteaDisplayName),
|
||||||
}
|
}
|
||||||
|
|
||||||
if earlyError != "" {
|
if earlyError != "" {
|
||||||
|
@ -1046,6 +1050,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 != ""
|
||||||
|
enableOauthGitea := app.Config().GiteaOauth.ClientID != ""
|
||||||
|
|
||||||
oauthAccounts, err := app.db.GetOauthAccounts(r.Context(), u.ID)
|
oauthAccounts, err := app.db.GetOauthAccounts(r.Context(), u.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1060,10 +1065,12 @@ 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 "gitea":
|
||||||
|
enableOauthGitea = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
displayOauthSection := enableOauthSlack || enableOauthWriteAs || enableOauthGitLab || len(oauthAccounts) > 0
|
displayOauthSection := enableOauthSlack || enableOauthWriteAs || enableOauthGitLab || enableOauthGitea || len(oauthAccounts) > 0
|
||||||
|
|
||||||
obj := struct {
|
obj := struct {
|
||||||
*UserPage
|
*UserPage
|
||||||
|
@ -1077,6 +1084,8 @@ func viewSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) err
|
||||||
OauthWriteAs bool
|
OauthWriteAs bool
|
||||||
OauthGitLab bool
|
OauthGitLab bool
|
||||||
GitLabDisplayName string
|
GitLabDisplayName string
|
||||||
|
OauthGitea bool
|
||||||
|
GiteaDisplayName string
|
||||||
}{
|
}{
|
||||||
UserPage: NewUserPage(app, r, u, "Account Settings", flashes),
|
UserPage: NewUserPage(app, r, u, "Account Settings", flashes),
|
||||||
Email: fullUser.EmailClear(app.keys),
|
Email: fullUser.EmailClear(app.keys),
|
||||||
|
@ -1089,6 +1098,8 @@ func viewSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) err
|
||||||
OauthWriteAs: enableOauthWriteAs,
|
OauthWriteAs: enableOauthWriteAs,
|
||||||
OauthGitLab: enableOauthGitLab,
|
OauthGitLab: enableOauthGitLab,
|
||||||
GitLabDisplayName: config.OrDefaultString(app.Config().GitlabOauth.DisplayName, gitlabDisplayName),
|
GitLabDisplayName: config.OrDefaultString(app.Config().GitlabOauth.DisplayName, gitlabDisplayName),
|
||||||
|
OauthGitea: enableOauthGitea,
|
||||||
|
GiteaDisplayName: config.OrDefaultString(app.Config().GiteaOauth.DisplayName, giteaDisplayName),
|
||||||
}
|
}
|
||||||
|
|
||||||
showUserPage(w, "settings", obj)
|
showUserPage(w, "settings", obj)
|
||||||
|
|
|
@ -86,6 +86,15 @@ type (
|
||||||
CallbackProxyAPI string `ini:"callback_proxy_api"`
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
// AppCfg holds values that affect how the application functions
|
// AppCfg holds values that affect how the application functions
|
||||||
AppCfg struct {
|
AppCfg struct {
|
||||||
SiteName string `ini:"site_name"`
|
SiteName string `ini:"site_name"`
|
||||||
|
@ -138,6 +147,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"`
|
||||||
|
GiteaOauth GiteaOauthCfg `ini:"oauth.gitea"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
28
oauth.go
28
oauth.go
|
@ -208,6 +208,34 @@ func configureGitlabOauth(parentHandler *Handler, r *mux.Router, app *App) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func configureGiteaOauth(parentHandler *Handler, r *mux.Router, app *App) {
|
||||||
|
if app.Config().GiteaOauth.ClientID != "" {
|
||||||
|
callbackLocation := app.Config().App.Host + "/oauth/callback/gitea"
|
||||||
|
|
||||||
|
var callbackProxy *callbackProxyClient = nil
|
||||||
|
if app.Config().GiteaOauth.CallbackProxy != "" {
|
||||||
|
callbackProxy = &callbackProxyClient{
|
||||||
|
server: app.Config().GiteaOauth.CallbackProxyAPI,
|
||||||
|
callbackLocation: app.Config().App.Host + "/oauth/callback/gitea",
|
||||||
|
httpClient: config.DefaultHTTPClient(),
|
||||||
|
}
|
||||||
|
callbackLocation = app.Config().GiteaOauth.CallbackProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
address := config.OrDefaultString(app.Config().GiteaOauth.Host, giteaHost)
|
||||||
|
oauthClient := giteaOauthClient{
|
||||||
|
ClientID: app.Config().GiteaOauth.ClientID,
|
||||||
|
ClientSecret: app.Config().GiteaOauth.ClientSecret,
|
||||||
|
ExchangeLocation: address + "/login/oauth/access_token",
|
||||||
|
InspectLocation: address + "/api/v1/user",
|
||||||
|
AuthLocation: address + "/login/oauth/authorize",
|
||||||
|
HttpClient: config.DefaultHTTPClient(),
|
||||||
|
CallbackLocation: callbackLocation,
|
||||||
|
}
|
||||||
|
configureOauthRoutes(parentHandler, r, app, oauthClient, callbackProxy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func configureOauthRoutes(parentHandler *Handler, r *mux.Router, app *App, oauthClient oauthClient, callbackProxy *callbackProxyClient) {
|
func configureOauthRoutes(parentHandler *Handler, r *mux.Router, app *App, oauthClient oauthClient, callbackProxy *callbackProxyClient) {
|
||||||
handler := &oauthHandler{
|
handler := &oauthHandler{
|
||||||
Config: app.Config(),
|
Config: app.Config(),
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
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 (
|
||||||
|
giteaHost = "https://source.gyt.is"
|
||||||
|
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", "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 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", "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
|
||||||
|
}
|
|
@ -36,6 +36,10 @@ hr.short {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
}
|
}
|
||||||
|
#gitea-login {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
|
@ -46,7 +50,7 @@ hr.short {
|
||||||
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}}
|
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}}
|
||||||
</ul>{{end}}
|
</ul>{{end}}
|
||||||
|
|
||||||
{{ if or .OauthSlack .OauthWriteAs .OauthGitlab }}
|
{{ if or .OauthSlack .OauthWriteAs .OauthGitlab .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>
|
||||||
|
@ -57,6 +61,9 @@ hr.short {
|
||||||
{{ 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 .OauthGitea }}
|
||||||
|
<a class="btn cta loginbtn" id="gitea-login" href="/oauth/gitea">Sign in with <strong>{{.GiteaDisplayName}}</strong></a>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="or">
|
<div class="or">
|
||||||
|
|
|
@ -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())
|
||||||
|
configureGiteaOauth(handler, write, apper.App())
|
||||||
|
|
||||||
// Set up dyamic page handlers
|
// Set up dyamic page handlers
|
||||||
// Handle auth
|
// Handle auth
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
|
@ -93,7 +93,7 @@ h3 { font-weight: normal; }
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ if or .OauthSlack .OauthWriteAs .OauthGitLab }}
|
{{ if or .OauthSlack .OauthWriteAs .OauthGitLab .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>
|
||||||
|
@ -122,6 +122,14 @@ h3 { font-weight: normal; }
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ if .OauthGitea }}
|
||||||
|
<div class="section oauth-provider">
|
||||||
|
<img src="/img/mark/gitea.png" alt="Gitea" />
|
||||||
|
<a class="btn cta loginbtn" id="gitea-login" href="/oauth/gitea?attach=t">
|
||||||
|
Link <strong>{{.GiteaDisplayName}}</strong>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
Loading…
Reference in New Issue