add Gitlab OAuth
This commit is contained in:
parent
bad970c60a
commit
c292512b9d
|
@ -308,6 +308,7 @@ func viewLogin(app *App, w http.ResponseWriter, r *http.Request) error {
|
||||||
LoginUsername string
|
LoginUsername string
|
||||||
OauthSlack bool
|
OauthSlack bool
|
||||||
OauthWriteAs bool
|
OauthWriteAs bool
|
||||||
|
OauthGitlab bool
|
||||||
}{
|
}{
|
||||||
pageForReq(app, r),
|
pageForReq(app, r),
|
||||||
r.FormValue("to"),
|
r.FormValue("to"),
|
||||||
|
@ -316,6 +317,7 @@ func viewLogin(app *App, w http.ResponseWriter, r *http.Request) error {
|
||||||
getTempInfo(app, "login-user", r, w),
|
getTempInfo(app, "login-user", r, w),
|
||||||
app.Config().SlackOauth.ClientID != "",
|
app.Config().SlackOauth.ClientID != "",
|
||||||
app.Config().WriteAsOauth.ClientID != "",
|
app.Config().WriteAsOauth.ClientID != "",
|
||||||
|
app.Config().GitlabOauth.ClientID != "",
|
||||||
}
|
}
|
||||||
|
|
||||||
if earlyError != "" {
|
if earlyError != "" {
|
||||||
|
|
|
@ -69,6 +69,16 @@ type (
|
||||||
CallbackProxyAPI string `ini:"callback_proxy_api"`
|
CallbackProxyAPI string `ini:"callback_proxy_api"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GitlabOauthCfg struct {
|
||||||
|
ClientID string `ini:"client_id"`
|
||||||
|
ClientSecret string `ini:"client_secret"`
|
||||||
|
AuthLocation string `ini:"auth_location"`
|
||||||
|
TokenLocation string `ini:"token_location"`
|
||||||
|
InspectLocation string `ini:"inspect_location"`
|
||||||
|
CallbackProxy string `ini:"callback_proxy"`
|
||||||
|
CallbackProxyAPI string `ini:"callback_proxy_api"`
|
||||||
|
}
|
||||||
|
|
||||||
SlackOauthCfg struct {
|
SlackOauthCfg struct {
|
||||||
ClientID string `ini:"client_id"`
|
ClientID string `ini:"client_id"`
|
||||||
ClientSecret string `ini:"client_secret"`
|
ClientSecret string `ini:"client_secret"`
|
||||||
|
@ -128,6 +138,7 @@ type (
|
||||||
App AppCfg `ini:"app"`
|
App AppCfg `ini:"app"`
|
||||||
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"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2512,7 +2512,7 @@ func (db *datastore) GetCollectionLastPostTime(id int64) (*time.Time, error) {
|
||||||
|
|
||||||
func (db *datastore) GenerateOAuthState(ctx context.Context, provider, clientID string) (string, error) {
|
func (db *datastore) GenerateOAuthState(ctx context.Context, provider, clientID string) (string, error) {
|
||||||
state := store.Generate62RandomString(24)
|
state := store.Generate62RandomString(24)
|
||||||
_, err := db.ExecContext(ctx, "INSERT INTO oauth_client_states (state, provider, client_id, used, created_at) VALUES (?, ?, ?, FALSE, NOW())", state, provider, clientID)
|
_, err := db.ExecContext(ctx, "INSERT INTO oauth_client_states (state, provider, client_id, used, created_at) VALUES (?, ?, ?, FALSE, " + db.now() + ")", state, provider, clientID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("unable to record oauth client state: %w", err)
|
return "", fmt.Errorf("unable to record oauth client state: %w", err)
|
||||||
}
|
}
|
||||||
|
|
29
oauth.go
29
oauth.go
|
@ -149,7 +149,7 @@ func configureWriteAsOauth(parentHandler *Handler, r *mux.Router, app *App) {
|
||||||
callbackLocation: app.Config().App.Host + "/oauth/callback/write.as",
|
callbackLocation: app.Config().App.Host + "/oauth/callback/write.as",
|
||||||
httpClient: config.DefaultHTTPClient(),
|
httpClient: config.DefaultHTTPClient(),
|
||||||
}
|
}
|
||||||
callbackLocation = app.Config().SlackOauth.CallbackProxy
|
callbackLocation = app.Config().WriteAsOauth.CallbackProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
oauthClient := writeAsOauthClient{
|
oauthClient := writeAsOauthClient{
|
||||||
|
@ -165,6 +165,33 @@ func configureWriteAsOauth(parentHandler *Handler, r *mux.Router, app *App) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func configureGitlabOauth(parentHandler *Handler, r *mux.Router, app *App) {
|
||||||
|
if app.Config().GitlabOauth.ClientID != "" {
|
||||||
|
callbackLocation := app.Config().App.Host + "/oauth/callback/gitlab"
|
||||||
|
|
||||||
|
var callbackProxy *callbackProxyClient = nil
|
||||||
|
if app.Config().GitlabOauth.CallbackProxy != "" {
|
||||||
|
callbackProxy = &callbackProxyClient{
|
||||||
|
server: app.Config().GitlabOauth.CallbackProxyAPI,
|
||||||
|
callbackLocation: app.Config().App.Host + "/oauth/callback/gitlab",
|
||||||
|
httpClient: config.DefaultHTTPClient(),
|
||||||
|
}
|
||||||
|
callbackLocation = app.Config().GitlabOauth.CallbackProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
oauthClient := gitlabOauthClient{
|
||||||
|
ClientID: app.Config().GitlabOauth.ClientID,
|
||||||
|
ClientSecret: app.Config().GitlabOauth.ClientSecret,
|
||||||
|
ExchangeLocation: config.OrDefaultString(app.Config().GitlabOauth.TokenLocation, gitlabExchangeLocation),
|
||||||
|
InspectLocation: config.OrDefaultString(app.Config().GitlabOauth.InspectLocation, gitlabIdentityLocation),
|
||||||
|
AuthLocation: config.OrDefaultString(app.Config().GitlabOauth.AuthLocation, gitlabAuthLocation),
|
||||||
|
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,116 @@
|
||||||
|
package writefreely
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type gitlabOauthClient struct {
|
||||||
|
ClientID string
|
||||||
|
ClientSecret string
|
||||||
|
AuthLocation string
|
||||||
|
ExchangeLocation string
|
||||||
|
InspectLocation string
|
||||||
|
CallbackLocation string
|
||||||
|
HttpClient HttpClient
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ oauthClient = gitlabOauthClient{}
|
||||||
|
|
||||||
|
const (
|
||||||
|
gitlabAuthLocation = "https://gitlab.com/oauth/authorize"
|
||||||
|
gitlabExchangeLocation = "https://gitlab.com/oauth/token"
|
||||||
|
gitlabIdentityLocation = "https://gitlab.com/api/v4/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c gitlabOauthClient) GetProvider() string {
|
||||||
|
return "gitlab"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c gitlabOauthClient) GetClientID() string {
|
||||||
|
return c.ClientID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c gitlabOauthClient) GetCallbackLocation() string {
|
||||||
|
return c.CallbackLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c gitlabOauthClient) 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 gitlabOauthClient) 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 gitlabOauthClient) 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
|
||||||
|
}
|
|
@ -32,6 +32,10 @@ hr.short {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
}
|
}
|
||||||
|
#gitlab-login {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
|
@ -42,7 +46,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 }}
|
{{ if or .OauthSlack .OauthWriteAs .OauthGitlab }}
|
||||||
<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>
|
||||||
|
@ -50,6 +54,9 @@ hr.short {
|
||||||
{{ if .OauthWriteAs }}
|
{{ if .OauthWriteAs }}
|
||||||
<a class="btn cta loginbtn" id="writeas-login" href="/oauth/write.as">Sign in with <strong>Write.as</strong></a>
|
<a class="btn cta loginbtn" id="writeas-login" href="/oauth/write.as">Sign in with <strong>Write.as</strong></a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ if .OauthGitlab }}
|
||||||
|
<a class="btn cta loginbtn" id="gitlab-login" href="/oauth/gitlab">Sign in with <strong>GitLab</strong></a>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="or">
|
<div class="or">
|
||||||
|
|
|
@ -75,6 +75,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())
|
||||||
|
|
||||||
// Set up dyamic page handlers
|
// Set up dyamic page handlers
|
||||||
// Handle auth
|
// Handle auth
|
||||||
|
|
Loading…
Reference in New Issue