diff --git a/README.md b/README.md new file mode 100644 index 0000000..76a6de5 --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# SCM OAuth Provider + +This is a lightweight Go server for handling OAuth flows with Gitea, Gitlab, Bitbucket, GitHub. + +> Note: My primary use case is providing OAuth between Gitea and NetlifyCMS. Other SCMs are untested as of now. + +## Setup + +Open the repo and build the service: + +``` +go build -o oauth-provider . +``` + +Deploy the binary to your server. + +> Dockerfile is coming soon + + +## Config + +The service needs some minimal configuration set before it can run. +On the server or the location you are running the service, create a config file: + +``` +mkdir ./env +touch ./env/config +``` + +The config file is TOML based. You can see a complete example in this repo at `./env/sample.config` + +``` +[runtime] +# Not used anywhere yet, for information only +environment="development" + +[server] +# The hostname to serve from; Your external app will connect to the OAuth provider via this URL +host="localhost" +# The port to serve from; Used in conjunction with [server.host] to create a complete URL +port="3000" +# Used with OAuth provider sessions +sessionSecret="super-secret" +``` + +For each CMS, there are some required settings: + +``` +[gitea] +# OAuth Key and Secret generated on the SCM site +key="" +secret="" +# URL of the SCM instance +baseUrl="https://gitea.company.com" +# URI of the authorize endpoint (e.g for Gitea, this is shown when creating the OAuth application) +authUri="login/oauth/authorize" +# URI of the access_token endpoint (e.g for Gitea, this is shown when creating the OAuth application) +accessTokenUri="login/oauth/access_token" +# URI of the authorize endpoint if overridden (e.g for Gitea, this is shown when creating the OAuth application) +userUri="api/v1/user" +# Callback URL for the SCM, where it will redirect the user after they authorise. This needs to match what was given when creating the OAuth application. +callbackUri="http://localhost:3000/callback/gitea" +``` + + +### Credits + +Inspiration taken from https://github.com/igk1972/netlify-cms-oauth-provider-go \ No newline at end of file diff --git a/main.go b/main.go index 00bd988..38df46b 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "html/template" "net" @@ -21,8 +22,18 @@ import ( ) var ( - serviceName = "netlify-cms-oauth-provider" + serviceName = "oauth-provider" msgTemplate = `{{.}}` + resTemplate = ` + + + ` log *logrus.Entry config *viper.Viper @@ -45,7 +56,7 @@ func initProviders() { ) type settings struct { - key, secret, baseURL, callbackURL, authURI, accessTokenURI, userURI string + key, secret, BaseURL, CallbackURL, AuthURI, AccessTokenURI, UserURI string } log.Info("initialising providers") @@ -55,11 +66,11 @@ func initProviders() { return settings{ key: config.GetString(name + ".key"), secret: config.GetString(name + ".secret"), - baseURL: baseURL, - authURI: fmt.Sprintf("%s/%s", baseURL, config.GetString(name+".authURI")), - accessTokenURI: fmt.Sprintf("%s/%s", baseURL, config.GetString(name+".accessTokenURI")), - userURI: fmt.Sprintf("%s/%s", baseURL, config.GetString(name+".userURI")), - callbackURL: config.GetString(name + ".callbackURI"), + BaseURL: baseURL, + AuthURI: fmt.Sprintf("%s/%s", baseURL, config.GetString(name+".authURI")), + AccessTokenURI: fmt.Sprintf("%s/%s", baseURL, config.GetString(name+".accessTokenURI")), + UserURI: fmt.Sprintf("%s/%s", baseURL, config.GetString(name+".userURI")), + CallbackURL: config.GetString(name + ".callbackURI"), } } @@ -67,11 +78,12 @@ func initProviders() { log.Info("- adding gitea provider") var p goth.Provider s := getProviderSetings("gitea") - if s.authURI != "" { - log.Infof("-- with custom settings %+v", s) - p = gitea.NewCustomisedURL(s.key, s.secret, s.callbackURL, s.authURI, s.accessTokenURI, s.userURI) + if s.AuthURI != "" { + out, _ := json.MarshalIndent(s, "", " ") + log.Infof("-- with custom settings %s", string(out)) + p = gitea.NewCustomisedURL(s.key, s.secret, s.CallbackURL, s.AuthURI, s.AccessTokenURI, s.UserURI) } else { - p = gitea.New(s.key, s.secret, s.callbackURL) + p = gitea.New(s.key, s.secret, s.CallbackURL) } providers = append(providers, p) } @@ -80,11 +92,12 @@ func initProviders() { log.Info("- adding gitlab provider") var p goth.Provider s := getProviderSetings("gitlab") - if s.authURI != "" { - log.Infof("-- with custom settings %+v", s) - p = gitlab.NewCustomisedURL(s.key, s.secret, s.callbackURL, s.authURI, s.accessTokenURI, s.userURI) + if s.AuthURI != "" { + out, _ := json.MarshalIndent(s, "", " ") + log.Infof("-- with custom settings %s", string(out)) + p = gitlab.NewCustomisedURL(s.key, s.secret, s.CallbackURL, s.AuthURI, s.AccessTokenURI, s.UserURI) } else { - p = gitlab.New(s.key, s.secret, s.callbackURL) + p = gitlab.New(s.key, s.secret, s.CallbackURL) } providers = append(providers, p) } @@ -93,7 +106,7 @@ func initProviders() { log.Info("- adding github provider") var p goth.Provider s := getProviderSetings("github") - p = github.New(s.key, s.secret, s.callbackURL) + p = github.New(s.key, s.secret, s.CallbackURL) providers = append(providers, p) } @@ -101,7 +114,7 @@ func initProviders() { log.Info("- adding bitbucket provider") var p goth.Provider s := getProviderSetings("bitbucket") - p = bitbucket.New(s.key, s.secret, s.callbackURL) + p = bitbucket.New(s.key, s.secret, s.CallbackURL) providers = append(providers, p) } @@ -109,37 +122,6 @@ func initProviders() { goth.UseProviders(providers...) } -const ( - script = `` -) - func main() { log = logrus.New().WithFields(logrus.Fields{ "service": serviceName, @@ -157,6 +139,8 @@ func main() { }) r.HandleFunc("/callback/{provider}", func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + provider, err := gothic.GetProviderName(r) if err != nil { log.Errorf("callback: GetProviderName failed %v", err) @@ -168,16 +152,18 @@ func main() { log.Errorf("callback: CompleteUserAuth failed %v", err) return } - log.Info("logged in user") - // t, _ := template.New("msg").Parse(msgTemplate) - // t.Execute(w, fmt.Sprintf("Connected with UserID '%s'", user.UserID)) - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.WriteHeader(http.StatusOK) - result := fmt.Sprintf(`{"token":"%s", "provider":"%s"}`, user.AccessToken, user.Provider) + log.Infof("logged in user to '%s'\n", vars["provider"]) + t, _ := template.New("res").Parse(resTemplate) - log.Info("details: %+v", user) - w.Write([]byte(fmt.Sprintf(script, "success", provider, result))) + data := struct { + Provider string + OAuthResult string + }{ + Provider: fmt.Sprintf(`authorizing:%s`, provider), + OAuthResult: fmt.Sprintf(`authorization:%s:%s:{"token":"%s", "provider":"%s"}`, provider, "success", user.AccessToken, user.Provider), + } + t.Execute(w, data) }).Methods("GET") // redirect to correct auth/{provider} URL if Auth request is submited with a query param '&provider=X' @@ -191,7 +177,9 @@ func main() { }).Methods("GET") r.HandleFunc("/auth/{provider}", func(w http.ResponseWriter, r *http.Request) { - log.Infof("handling auth provider request '%s'\n", r) + vars := mux.Vars(r) + log.Infof("handling auth provider request '%s'\n", vars["provider"]) + if gothUser, err := gothic.CompleteUserAuth(w, r); err == nil { t, _ := template.New("msg").Parse(msgTemplate) t.Execute(w, fmt.Sprintf("Connected to existing session with UserID '%s'", gothUser.UserID)) @@ -201,7 +189,9 @@ func main() { }).Methods("GET") r.HandleFunc("/logout/{provider}", func(w http.ResponseWriter, r *http.Request) { - log.Infof("logout with '%s'\n", r) + vars := mux.Vars(r) + log.Infof("logout from '%s'\n", vars["provider"]) + gothic.Logout(w, r) w.Header().Set("Location", "/") w.WriteHeader(http.StatusTemporaryRedirect)