diff --git a/README.md b/README.md index 0be6157..97b58ab 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,19 @@ Then add your `app_mode`, `app_port`, `app_log_level`, `github_token`, `github_w "github_token": "...", "github_webhook_secret": "...", "repository_author": "Clivern", - "repository_name": "Hamster" + "repository_name": "Hamster", + + "app_domain": "example.com", + "github_app_client_id": "..", + "github_app_redirect_uri": "..", + "github_app_allow_signup": "false", + "github_app_scope": "..", + "github_app_client_secret": "..", } ``` +You can config `app_domain` and the rest of github app configs `github_app_*` in case you need a github app not a personal bot. + Add a new webhook from `Settings > Webhooks`, Set the `Payload URL` to be `https://hamster.com/listen`, `Content type` as `JSON` and Add Your Webhook Secret. And then run the application diff --git a/config.json b/config.json index c000687..b24a031 100644 --- a/config.json +++ b/config.json @@ -5,5 +5,11 @@ "github_token": "..", "github_webhook_secret": "..", "repository_author": "..", - "repository_name": ".." + "repository_name": "..", + "app_domain": "example.com", + "github_app_client_id": "..", + "github_app_redirect_uri": "..", + "github_app_allow_signup": "false", + "github_app_scope": "..", + "github_app_client_secret": "..", } \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 882951b..14534cd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,4 +32,10 @@ services: - RepositoryName=Hamster - AppMode=prod - AppPort=8080 - - AppLogLevel=info \ No newline at end of file + - AppLogLevel=info + - GithubAppClientID=ValueHere + - GithubAppRedirectURI=ValueHere + - GithubAppAllowSignup=false + - GithubAppScope=ValueHere + - GithubAppClientSecret=ValueHere + - AppDomain=example.com \ No newline at end of file diff --git a/internal/app/controller/auth.go b/internal/app/controller/auth.go index 8e1a36b..bd97b18 100644 --- a/internal/app/controller/auth.go +++ b/internal/app/controller/auth.go @@ -5,11 +5,41 @@ package controller import ( + "github.com/clivern/hamster/pkg" "github.com/gin-gonic/gin" + "os" ) func Auth(c *gin.Context) { - c.JSON(200, gin.H{ - "status": "ok", - }) + + githubOauth := &pkg.GithubOAuthApp{ + ClientID: os.Getenv("GithubAppClientID"), + RedirectURI: os.Getenv("GithubAppRedirectURI"), + AllowSignup: os.Getenv("GithubAppAllowSignup"), + Scope: os.Getenv("GithubAppScope"), + ClientSecret: os.Getenv("GithubAppClientSecret"), + } + + state, err := c.Cookie("gh_oauth_state") + + if err == nil && state != "" { + githubOauth.SetState(state) + } + + ok, err := githubOauth.FetchAccessToken( + c.DefaultQuery("code", ""), + c.DefaultQuery("state", ""), + ) + + if ok && err == nil { + c.JSON(200, gin.H{ + "status": "ok", + "accessToken": githubOauth.GetAccessToken(), + }) + } else { + c.JSON(200, gin.H{ + "status": "not ok", + "error": err.Error(), + }) + } } diff --git a/internal/app/controller/login.go b/internal/app/controller/login.go index f536844..e1517d9 100644 --- a/internal/app/controller/login.go +++ b/internal/app/controller/login.go @@ -8,20 +8,27 @@ import ( "github.com/clivern/hamster/pkg" "github.com/gin-gonic/gin" "net/http" + "os" ) func Login(c *gin.Context) { + githubOauth := &pkg.GithubOAuthApp{ - ClientID: "ClientID", - RedirectURI: "RedirectURI", - Scope: "Scope", - State: "State", - AllowSignup: "AllowSignup", + ClientID: os.Getenv("GithubAppClientID"), + RedirectURI: os.Getenv("GithubAppRedirectURI"), + AllowSignup: os.Getenv("GithubAppAllowSignup"), + Scope: os.Getenv("GithubAppScope"), + ClientSecret: os.Getenv("GithubAppClientSecret"), + } + + state, err := c.Cookie("gh_oauth_state") + + if err != nil || state == "" { + githubOauth.GenerateState() + c.SetCookie("gh_oauth_state", githubOauth.GetState(), 3600, "/", os.Getenv("AppDomain"), true, true) + } else { + githubOauth.SetState(state) } - githubOauth.AddScope("scope1") - githubOauth.AddScope("scope2") - githubOauth.AddScope("scope3") - githubOauth.GenerateState() c.HTML(http.StatusOK, "login.tmpl", gin.H{ "title": "Hamster", diff --git a/pkg/configs.go b/pkg/configs.go index e5e9d6d..9999be4 100644 --- a/pkg/configs.go +++ b/pkg/configs.go @@ -12,13 +12,19 @@ import ( ) type Config struct { - GithubToken string `json:"github_token"` - GithubWebhookSecret string `json:"github_webhook_secret"` - RepositoryAuthor string `json:"repository_author"` - RepositoryName string `json:"repository_name"` - AppMode string `json:"app_mode"` - AppPort string `json:"app_port"` - AppLogLevel string `json:"app_log_level"` + GithubToken string `json:"github_token"` + GithubWebhookSecret string `json:"github_webhook_secret"` + RepositoryAuthor string `json:"repository_author"` + RepositoryName string `json:"repository_name"` + AppMode string `json:"app_mode"` + AppPort string `json:"app_port"` + AppLogLevel string `json:"app_log_level"` + AppDomain string `json:"app_domain"` + GithubAppClientID string `json:"github_app_client_id"` + GithubAppRedirectURI string `json:"github_app_redirect_uri"` + GithubAppAllowSignup string `json:"github_app_allow_signup"` + GithubAppScope string `json:"github_app_scope"` + GithubAppClientSecret string `json:"github_app_client_secret"` } func (e *Config) Load(file string) (bool, error) { @@ -53,6 +59,12 @@ func (e *Config) Cache() { os.Setenv("AppMode", e.AppMode) os.Setenv("AppLogLevel", e.AppLogLevel) os.Setenv("AppPort", e.AppPort) + os.Setenv("GithubAppClientID", e.GithubAppClientID) + os.Setenv("GithubAppRedirectURI", e.GithubAppRedirectURI) + os.Setenv("GithubAppAllowSignup", e.GithubAppAllowSignup) + os.Setenv("GithubAppScope", e.GithubAppScope) + os.Setenv("GithubAppClientSecret", e.GithubAppClientSecret) + os.Setenv("AppDomain", e.AppDomain) } } diff --git a/pkg/oauth.go b/pkg/oauth.go index 191622e..ed609f6 100644 --- a/pkg/oauth.go +++ b/pkg/oauth.go @@ -7,7 +7,10 @@ package pkg import ( "crypto/rand" "encoding/hex" + "encoding/json" "fmt" + "io/ioutil" + "net/http" "strings" ) @@ -24,10 +27,11 @@ type GithubOAuthApp struct { State string `json:"state"` AllowSignup string `json:"allow_signup"` ClientSecret string `json:"client_secret"` + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` } type GithubOAuthClient struct { - Code string `json:"code"` AccessToken string `json:"access_token"` TokenType string `json:"token_type"` } @@ -43,6 +47,10 @@ func (e *GithubOAuthApp) GetState() string { return e.State } +func (e *GithubOAuthApp) SetState(state string) { + e.State = state +} + func (e *GithubOAuthApp) AddScope(scope string) { e.Scopes = append(e.Scopes, scope) e.Scope = strings.Join(e.Scopes, ",") @@ -75,13 +83,64 @@ func (e *GithubOAuthApp) RandomString(len int) (string, error) { return hex.EncodeToString(bytes), nil } -func (e *GithubOAuthClient) FetchAccessToken(code string, incomingState string, originalState string) (bool, error) { - if incomingState != originalState { - return false, fmt.Errorf("Invalid state provided %s, original one is %s", incomingState, originalState) +func (e *GithubOAuthApp) FetchAccessToken(code string, state string) (bool, error) { + + githubOAuthClient := &GithubOAuthClient{} + + if state != e.State { + return false, fmt.Errorf( + "Invalid state provided %s, original one is %s", + state, + e.State, + ) } + + url := fmt.Sprintf( + "%s?client_id=%s&client_secret=%s&code=%s&redirect_uri=%s&state=%s", + OAuthAccessToken, + e.ClientID, + e.ClientSecret, + code, + e.RedirectURI, + e.State, + ) + client := &http.Client{} + req, err := http.NewRequest("POST", url, nil) + + if err != nil { + return false, err + } + + resp, err := client.Do(req) + + if err != nil { + return false, err + } + + defer resp.Body.Close() + + bodyByte, err := ioutil.ReadAll(resp.Body) + + if err != nil { + return false, err + } + + err = json.Unmarshal(bodyByte, &githubOAuthClient) + + if err != nil { + return false, err + } + + e.AccessToken = githubOAuthClient.AccessToken + e.TokenType = githubOAuthClient.TokenType + return true, nil } -func (e *GithubOAuthClient) GetAccessToken() string { +func (e *GithubOAuthApp) GetAccessToken() string { return e.AccessToken } + +func (e *GithubOAuthApp) GetTokenType() string { + return e.TokenType +}