2
0

config.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. package oauth
  2. import (
  3. "context"
  4. "crypto/rand"
  5. "encoding/base64"
  6. "time"
  7. "github.com/porter-dev/porter/internal/models/integrations"
  8. "github.com/porter-dev/porter/internal/repository"
  9. "golang.org/x/oauth2"
  10. )
  11. type Config struct {
  12. ClientID string
  13. ClientSecret string
  14. Scopes []string
  15. BaseURL string
  16. }
  17. // GithubAppConf is standard oauth2 config but it need to keeps track of the app name and webhook secret
  18. type GithubAppConf struct {
  19. AppName string
  20. WebhookSecret string
  21. SecretPath string
  22. AppID int64
  23. oauth2.Config
  24. }
  25. const (
  26. GithubAuthURL string = "https://github.com/login/oauth/authorize"
  27. GithubTokenURL string = "https://github.com/login/oauth/access_token"
  28. DOAuthURL string = "https://cloud.digitalocean.com/v1/oauth/authorize"
  29. DOTokenURL string = "https://cloud.digitalocean.com/v1/oauth/token"
  30. GoogleAuthURL string = "https://accounts.google.com/o/oauth2/v2/auth"
  31. GoogleTokenURL string = "https://oauth2.googleapis.com/token"
  32. SlackAuthURL string = "https://slack.com/oauth/v2/authorize"
  33. SlackTokenURL string = "https://slack.com/api/oauth.v2.access"
  34. )
  35. func NewGithubClient(cfg *Config) *oauth2.Config {
  36. return &oauth2.Config{
  37. ClientID: cfg.ClientID,
  38. ClientSecret: cfg.ClientSecret,
  39. Endpoint: oauth2.Endpoint{
  40. AuthURL: GithubAuthURL,
  41. TokenURL: GithubTokenURL,
  42. },
  43. RedirectURL: cfg.BaseURL + "/api/oauth/github/callback",
  44. Scopes: cfg.Scopes,
  45. }
  46. }
  47. func NewGithubAppClient(cfg *Config, name string, secret string, secretPath string, appID int64) *GithubAppConf {
  48. return &GithubAppConf{
  49. AppName: name,
  50. WebhookSecret: secret,
  51. SecretPath: secretPath,
  52. AppID: appID,
  53. Config: oauth2.Config{
  54. ClientID: cfg.ClientID,
  55. ClientSecret: cfg.ClientSecret,
  56. Endpoint: oauth2.Endpoint{
  57. AuthURL: GithubAuthURL,
  58. TokenURL: GithubTokenURL,
  59. },
  60. RedirectURL: cfg.BaseURL + "/api/oauth/github-app/callback",
  61. Scopes: cfg.Scopes,
  62. },
  63. }
  64. }
  65. func NewDigitalOceanClient(cfg *Config) *oauth2.Config {
  66. return &oauth2.Config{
  67. ClientID: cfg.ClientID,
  68. ClientSecret: cfg.ClientSecret,
  69. Endpoint: oauth2.Endpoint{
  70. AuthURL: DOAuthURL,
  71. TokenURL: DOTokenURL,
  72. AuthStyle: oauth2.AuthStyleInParams,
  73. },
  74. RedirectURL: cfg.BaseURL + "/api/oauth/digitalocean/callback",
  75. Scopes: cfg.Scopes,
  76. }
  77. }
  78. func NewGoogleClient(cfg *Config) *oauth2.Config {
  79. return &oauth2.Config{
  80. ClientID: cfg.ClientID,
  81. ClientSecret: cfg.ClientSecret,
  82. Endpoint: oauth2.Endpoint{
  83. AuthURL: GoogleAuthURL,
  84. TokenURL: GoogleTokenURL,
  85. },
  86. RedirectURL: cfg.BaseURL + "/api/oauth/google/callback",
  87. Scopes: cfg.Scopes,
  88. }
  89. }
  90. func NewSlackClient(cfg *Config) *oauth2.Config {
  91. return &oauth2.Config{
  92. ClientID: cfg.ClientID,
  93. ClientSecret: cfg.ClientSecret,
  94. Endpoint: oauth2.Endpoint{
  95. AuthURL: SlackAuthURL,
  96. TokenURL: SlackTokenURL,
  97. },
  98. RedirectURL: cfg.BaseURL + "/api/oauth/slack/callback",
  99. Scopes: cfg.Scopes,
  100. }
  101. }
  102. // NewUpstashClient creates a new oauth2.Config for Upstash
  103. func NewUpstashClient(cfg *Config) oauth2.Config {
  104. return oauth2.Config{
  105. ClientID: cfg.ClientID,
  106. ClientSecret: cfg.ClientSecret,
  107. Endpoint: oauth2.Endpoint{
  108. AuthURL: "https://auth.upstash.com/authorize",
  109. TokenURL: "https://auth.upstash.com/oauth/token",
  110. },
  111. RedirectURL: cfg.BaseURL + "/api/oauth/upstash/callback",
  112. Scopes: cfg.Scopes,
  113. }
  114. }
  115. // NewNeonClient creates a new oauth2.Config for Neon
  116. func NewNeonClient(cfg *Config) oauth2.Config {
  117. return oauth2.Config{
  118. ClientID: cfg.ClientID,
  119. ClientSecret: cfg.ClientSecret,
  120. Endpoint: oauth2.Endpoint{
  121. AuthURL: "https://oauth2.neon.tech/oauth2/auth",
  122. TokenURL: "https://oauth2.neon.tech/oauth2/token",
  123. },
  124. RedirectURL: cfg.BaseURL + "/api/oauth/neon/callback",
  125. Scopes: cfg.Scopes,
  126. }
  127. }
  128. func CreateRandomState() string {
  129. b := make([]byte, 16)
  130. rand.Read(b)
  131. state := base64.URLEncoding.EncodeToString(b)
  132. return state
  133. }
  134. // MakeUpdateOAuthIntegrationTokenFunction creates a function to be passed to GetAccessToken that updates the OauthIntegration
  135. // if it needs to be updated
  136. func MakeUpdateOAuthIntegrationTokenFunction(
  137. o *integrations.OAuthIntegration,
  138. repo repository.Repository,
  139. ) func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
  140. return func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
  141. o.AccessToken = accessToken
  142. o.RefreshToken = refreshToken
  143. o.Expiry = expiry
  144. _, err := repo.OAuthIntegration().UpdateOAuthIntegration(o)
  145. return err
  146. }
  147. }
  148. // MakeUpdateGithubAppOauthIntegrationFunction creates a function to be passed to GetAccessToken that updates the GithubAppOauthIntegration
  149. // if it needs to be updated
  150. func MakeUpdateGithubAppOauthIntegrationFunction(
  151. o *integrations.GithubAppOAuthIntegration,
  152. repo repository.Repository,
  153. ) func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
  154. return func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
  155. o.AccessToken = accessToken
  156. o.RefreshToken = refreshToken
  157. o.Expiry = expiry
  158. _, err := repo.GithubAppOAuthIntegration().UpdateGithubAppOauthIntegration(o)
  159. return err
  160. }
  161. }
  162. // MakeUpdateGitlabAppOAuthIntegrationFunction creates a function to be passed to GetAccessToken that updates the GitlabAppOAuthIntegration
  163. // if it needs to be updated
  164. func MakeUpdateGitlabAppOAuthIntegrationFunction(
  165. projectID uint,
  166. o *integrations.GitlabAppOAuthIntegration,
  167. repo repository.Repository,
  168. ) func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
  169. return func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
  170. o, err := repo.OAuthIntegration().ReadOAuthIntegration(projectID, o.OAuthIntegrationID)
  171. if err != nil {
  172. return err
  173. }
  174. o.AccessToken = accessToken
  175. o.RefreshToken = refreshToken
  176. o.Expiry = expiry
  177. _, err = repo.OAuthIntegration().UpdateOAuthIntegration(o)
  178. return err
  179. }
  180. }
  181. // GetAccessToken retrieves an access token for a given client. It updates the
  182. // access token in the DB if necessary
  183. func GetAccessToken(
  184. prevToken integrations.SharedOAuthModel,
  185. conf *oauth2.Config,
  186. updateToken func(accessToken []byte, refreshToken []byte, expiry time.Time) error,
  187. ) (string, *time.Time, error) {
  188. expiry := prevToken.Expiry
  189. if conf.Endpoint.AuthURL == DOAuthURL && expiry.IsZero() {
  190. // manually set the expiry so refresh token is used
  191. expiry = time.Now().Add(-1 * time.Minute)
  192. }
  193. tokSource := conf.TokenSource(context.TODO(), &oauth2.Token{
  194. AccessToken: string(prevToken.AccessToken),
  195. RefreshToken: string(prevToken.RefreshToken),
  196. TokenType: "Bearer",
  197. Expiry: expiry,
  198. })
  199. token, err := tokSource.Token()
  200. if err != nil {
  201. return "", nil, err
  202. }
  203. if token.AccessToken != string(prevToken.AccessToken) {
  204. err := updateToken([]byte(token.AccessToken), []byte(token.RefreshToken), token.Expiry)
  205. if err != nil {
  206. return "", nil, err
  207. }
  208. }
  209. return token.AccessToken, &token.Expiry, nil
  210. }