B: vendors working. How to make rules work
This commit is contained in:
parent
d36eb955ab
commit
5c7a72cf0f
|
@ -8,11 +8,10 @@ import (
|
|||
)
|
||||
|
||||
type config struct {
|
||||
Mongo *MongoConfig `mapstructure:"mongo"`
|
||||
YoutubeConfig *YoutubeConfig `mapstructure:"youtube"`
|
||||
PcoConfig *PcoConfig `mapstructure:"pco"`
|
||||
JwtSecret string `mapstructure:"jwt_secret"`
|
||||
Env string `mapstructure:"env"`
|
||||
Mongo *MongoConfig `mapstructure:"mongo"`
|
||||
Vendors map[string]*VendorConfig `mapstructure:"vendors"`
|
||||
JwtSecret string `mapstructure:"jwt_secret"`
|
||||
Env string `mapstructure:"env"`
|
||||
}
|
||||
|
||||
type MongoConfig struct {
|
||||
|
@ -21,30 +20,21 @@ type MongoConfig struct {
|
|||
EntCol string `mapstructure:"ent_col"`
|
||||
}
|
||||
|
||||
type YoutubeConfig struct {
|
||||
ClientId string `mapstructure:"client_id"`
|
||||
ClientSecret string `mapstructure:"client_secret"`
|
||||
Scopes []string `mapstructure:"scopes"`
|
||||
AuthUri string `mapstructure:"auth_uri"`
|
||||
TokenUri string `mapstructure:"token_uri"`
|
||||
scope string
|
||||
type VendorConfig struct {
|
||||
ClientId string `mapstructure:"client_id"`
|
||||
ClientSecret string `mapstructure:"client_secret"`
|
||||
Scopes []string `mapstructure:"scopes"`
|
||||
AuthUri string `mapstructure:"auth_uri"`
|
||||
TokenUri string `mapstructure:"token_uri"`
|
||||
RefreshEncode string `mapstructure:"refresh_encode"`
|
||||
scope string
|
||||
}
|
||||
|
||||
func (yt *YoutubeConfig) Scope() string {
|
||||
if yt.scope == "" {
|
||||
for i, str := range yt.Scopes {
|
||||
yt.Scopes[i] = fmt.Sprintf("https://www.googleapis.com%s", str)
|
||||
}
|
||||
yt.scope = strings.Join(yt.Scopes, " ")
|
||||
func (pco *VendorConfig) Scope() string {
|
||||
if pco.scope == "" {
|
||||
pco.scope = strings.Join(pco.Scopes, " ")
|
||||
}
|
||||
return yt.scope
|
||||
}
|
||||
|
||||
type PcoConfig struct {
|
||||
ClientId string `mapstructure:"client_id"`
|
||||
ClientSecret string `mapstructure:"client_secret"`
|
||||
AuthUri string `mapstructure:"auth_uri"`
|
||||
TokenUri string `mapstructure:"token_uri"`
|
||||
return pco.scope
|
||||
}
|
||||
|
||||
var cfg *config
|
||||
|
@ -65,6 +55,11 @@ func Init() {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%v\n", cfg)
|
||||
for key, value := range cfg.Vendors {
|
||||
fmt.Printf("%s: %v\n", key, value)
|
||||
}
|
||||
}
|
||||
|
||||
func Config() *config {
|
||||
|
|
|
@ -50,4 +50,5 @@ func BuildRouter(r *gin.Engine) {
|
|||
|
||||
pco := vendor.Group("/pco")
|
||||
pco.POST("/initiate", InitiatePCOOuath)
|
||||
pco.GET("/callback", RecievePCOOuath)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,128 @@
|
|||
package controllers
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/config"
|
||||
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/db/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const PCO_REDIRECT_URI = "https://capstone.preston-baxter.com:8080/vendor/pco/callback"
|
||||
|
||||
func InitiatePCOOuath(c *gin.Context) {
|
||||
c.String(200, "ok")
|
||||
conf := config.Config()
|
||||
vendorConfig := conf.Vendors[models.PCO_VENDOR_NAME]
|
||||
|
||||
init_url, err := url.Parse(vendorConfig.AuthUri)
|
||||
if err != nil {
|
||||
//we should not get here
|
||||
panic(err)
|
||||
}
|
||||
|
||||
q := init_url.Query()
|
||||
q.Add("client_id", vendorConfig.ClientId)
|
||||
q.Add("redirect_uri", PCO_REDIRECT_URI)
|
||||
q.Add("response_type", "code")
|
||||
q.Add("scope", vendorConfig.Scope())
|
||||
init_url.RawQuery = q.Encode()
|
||||
|
||||
c.Redirect(302, init_url.String())
|
||||
}
|
||||
|
||||
func RecievePCOOuath(c *gin.Context) {
|
||||
conf := config.Config()
|
||||
vendorConfig := conf.Vendors[models.PCO_VENDOR_NAME]
|
||||
user := getUserFromContext(c)
|
||||
|
||||
if user == nil {
|
||||
log.Error("Unable to find user in context")
|
||||
c.AbortWithStatus(502)
|
||||
}
|
||||
|
||||
code := c.Query("code")
|
||||
//validate returned code
|
||||
if code == "" {
|
||||
log.Error("Youtube OAuth response did not contain a code. Possible CSRF")
|
||||
c.AbortWithStatus(502)
|
||||
return
|
||||
}
|
||||
|
||||
client := http.Client{}
|
||||
|
||||
token_url, err := url.Parse(vendorConfig.TokenUri)
|
||||
if err != nil {
|
||||
//we should not get here
|
||||
panic(err)
|
||||
}
|
||||
|
||||
//Make request to google for credentials
|
||||
q := token_url.Query()
|
||||
|
||||
q.Add("code", code)
|
||||
q.Add("client_id", vendorConfig.ClientId)
|
||||
q.Add("client_secret", vendorConfig.ClientSecret)
|
||||
q.Add("redirect_uri", PCO_REDIRECT_URI)
|
||||
q.Add("grant_type", "authorization_code")
|
||||
|
||||
req, err := http.NewRequest("POST", token_url.String(), strings.NewReader(q.Encode()))
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("Failed to generate request with the following url: '%s'", token_url.String())
|
||||
c.AbortWithStatus(502)
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("Failed to make request to the following url: '%s'", token_url.String())
|
||||
c.AbortWithStatus(502)
|
||||
return
|
||||
}
|
||||
|
||||
rawBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("Failed to read body from the following url: '%s'", token_url.String())
|
||||
c.AbortWithStatus(502)
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
log.Errorf("Response failed with status code: %d. Error: %s", resp.StatusCode ,string(rawBody))
|
||||
c.AbortWithStatus(502)
|
||||
return
|
||||
}
|
||||
|
||||
oauthResp := &models.OauthCredential{}
|
||||
err = json.Unmarshal(rawBody, oauthResp)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("Failed to Unmarshal response from the following url: '%s'", token_url.String())
|
||||
c.AbortWithStatus(502)
|
||||
}
|
||||
log.Infof("oauthResp: %v", *oauthResp)
|
||||
//Set expires at time but shave some time off to refresh token before expire date
|
||||
oauthResp.ExpiresAt = time.Now().Add(time.Duration(oauthResp.ExpiresIn)*time.Second - 10)
|
||||
|
||||
//store credentials
|
||||
vendor := &models.VendorAccount{
|
||||
UserId: user.Id,
|
||||
OauthCredentials: oauthResp,
|
||||
Name: models.PCO_VENDOR_NAME,
|
||||
}
|
||||
|
||||
err = mongo.SaveModel(vendor)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("Failed to save credentials for user: %s", user.Email)
|
||||
c.AbortWithStatus(502)
|
||||
return
|
||||
}
|
||||
|
||||
c.Redirect(302, "/dashboard")
|
||||
|
||||
}
|
||||
|
|
|
@ -13,12 +13,13 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const REDIRECT_URI = "https://capstone.preston-baxter.com:8080/vendor/youtube/callback"
|
||||
const YOUTUBE_REDIRECT_URI = "https://capstone.preston-baxter.com:8080/vendor/youtube/callback"
|
||||
|
||||
func InitiateYoutubeOuath(c *gin.Context) {
|
||||
conf := config.Config()
|
||||
vendorConfig := conf.Vendors[models.YOUTUBE_VENDOR_NAME]
|
||||
|
||||
init_url, err := url.Parse(conf.YoutubeConfig.AuthUri)
|
||||
init_url, err := url.Parse(vendorConfig.AuthUri)
|
||||
if err != nil {
|
||||
//we should not get here
|
||||
panic(err)
|
||||
|
@ -26,10 +27,10 @@ func InitiateYoutubeOuath(c *gin.Context) {
|
|||
|
||||
q := init_url.Query()
|
||||
//https://developers.google.com/youtube/v3/guides/auth/server-side-web-apps#httprest_1
|
||||
q.Add("client_id", conf.YoutubeConfig.ClientId)
|
||||
q.Add("redirect_uri", REDIRECT_URI)
|
||||
q.Add("client_id", vendorConfig.ClientId)
|
||||
q.Add("redirect_uri", YOUTUBE_REDIRECT_URI)
|
||||
q.Add("response_type", "code")
|
||||
q.Add("scope", conf.YoutubeConfig.Scope())
|
||||
q.Add("scope", vendorConfig.Scope())
|
||||
q.Add("access_type", "offline")
|
||||
//used to prevent CSRF
|
||||
q.Add("state", getAuthHash(c))
|
||||
|
@ -40,6 +41,7 @@ func InitiateYoutubeOuath(c *gin.Context) {
|
|||
|
||||
func ReceiveYoutubeOauth(c *gin.Context) {
|
||||
conf := config.Config()
|
||||
vendorConfig := conf.Vendors[models.PCO_VENDOR_NAME]
|
||||
user := getUserFromContext(c)
|
||||
|
||||
if user == nil {
|
||||
|
@ -65,7 +67,7 @@ func ReceiveYoutubeOauth(c *gin.Context) {
|
|||
|
||||
client := http.Client{}
|
||||
|
||||
token_url, err := url.Parse(conf.YoutubeConfig.TokenUri)
|
||||
token_url, err := url.Parse(vendorConfig.TokenUri)
|
||||
if err != nil {
|
||||
//we should not get here
|
||||
panic(err)
|
||||
|
@ -75,9 +77,9 @@ func ReceiveYoutubeOauth(c *gin.Context) {
|
|||
q := token_url.Query()
|
||||
|
||||
q.Add("code", code)
|
||||
q.Add("client_id", conf.YoutubeConfig.ClientId)
|
||||
q.Add("client_secret", conf.YoutubeConfig.ClientSecret)
|
||||
q.Add("redirect_uri", REDIRECT_URI)
|
||||
q.Add("client_id", vendorConfig.ClientId)
|
||||
q.Add("client_secret", vendorConfig.ClientSecret)
|
||||
q.Add("redirect_uri", YOUTUBE_REDIRECT_URI)
|
||||
q.Add("grant_type", "authorization_code")
|
||||
|
||||
req, err := http.NewRequest("POST", token_url.String(), strings.NewReader(q.Encode()))
|
||||
|
@ -124,7 +126,7 @@ func ReceiveYoutubeOauth(c *gin.Context) {
|
|||
vendor := &models.VendorAccount{
|
||||
UserId: user.Id,
|
||||
OauthCredentials: oauthResp,
|
||||
Name: "youtube",
|
||||
Name: models.YOUTUBE_VENDOR_NAME,
|
||||
}
|
||||
|
||||
err = mongo.SaveModel(vendor)
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/config"
|
||||
)
|
||||
|
||||
type OauthCredential struct {
|
||||
AccessToken string `bson:"access_token,omitempty" json:"access_token,omitempty"`
|
||||
ExpiresIn int `bson:"expires_in,omitempty" json:"expires_in,omitempty"`
|
||||
ExpiresAt time.Time `bson:"expires_at,omitempty" json:"expires_at,omitempty"`
|
||||
TokenType string `bson:"token_type,omitempty" json:"token_type,omitempty"`
|
||||
Scope string `bson:"scope,omitempty" json:"scope,omitempty"`
|
||||
RefreshToken string `bson:"refresh_token,omitempty" json:"refresh_token,omitempty"`
|
||||
}
|
||||
|
||||
type OauthRefreshBody struct {
|
||||
ClientId string `json:"cleint_id"`
|
||||
ClientSecret string `json:"cleint_secret"`
|
||||
GrantType string `json:"grant_type"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
func (oc *OauthCredential) RefreshAccessToken(vendor string) error {
|
||||
conf := config.Config()
|
||||
vendorConfig := conf.Vendors[vendor]
|
||||
|
||||
refresh_url, err := url.Parse(vendorConfig.TokenUri)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var body io.Reader
|
||||
switch vendorConfig.RefreshEncode {
|
||||
case "json":
|
||||
refreshBody := OauthRefreshBody{
|
||||
ClientId: vendorConfig.ClientId,
|
||||
ClientSecret: vendorConfig.ClientSecret,
|
||||
GrantType: "refresh_token",
|
||||
RefreshToken: oc.RefreshToken,
|
||||
}
|
||||
raw, err := json.Marshal(&refreshBody)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
body = bytes.NewReader(raw)
|
||||
case "url":
|
||||
q := refresh_url.Query()
|
||||
q.Add("client_id", vendorConfig.ClientId)
|
||||
q.Add("client_secret", vendorConfig.ClientSecret)
|
||||
q.Add("code", oc.RefreshToken)
|
||||
q.Add("grant_type", "refresh_token")
|
||||
|
||||
body = strings.NewReader(q.Encode())
|
||||
default:
|
||||
panic(errors.New("Unkoown Encode Scheme"))
|
||||
}
|
||||
|
||||
client := http.Client{}
|
||||
req, err := http.NewRequest("POST", refresh_url.String(), body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rawBody, err := io.ReadAll(resp.Body)
|
||||
|
||||
err = json.Unmarshal(rawBody, oc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oc.ExpiresAt = time.Now().Add(time.Duration(oc.ExpiresIn)*time.Second - 10)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
const VENDOR_ACCOUNT_TYPE = "vendor_account"
|
||||
|
@ -13,14 +15,6 @@ const (
|
|||
PCO_VENDOR_NAME = "PCO"
|
||||
)
|
||||
|
||||
type OauthCredential struct {
|
||||
AccessToken string `bson:"access_token,omitempty" json:"access_token,omitempty"`
|
||||
ExpiresIn int `bson:"expires_in,omitempty" json:"expires_in,omitempty"`
|
||||
ExpiresAt time.Time `bson:"expires_at,omitempty" json:"expires_at,omitempty"`
|
||||
TokenType string `bson:"token_type,omitempty" json:"token_type,omitempty"`
|
||||
Scope string `bson:"scope,omitempty" json:"scope,omitempty"`
|
||||
RefreshToken string `bson:"refresh_token,omitempty" json:"refresh_token,omitempty"`
|
||||
}
|
||||
|
||||
type VendorAccount struct {
|
||||
*CommonFields `bson:"obj_info"`
|
||||
|
@ -49,3 +43,9 @@ func (va *VendorAccount) UpdateObjectInfo() {
|
|||
}
|
||||
va.UpdatedAt = now
|
||||
}
|
||||
|
||||
func (va *VendorAccount) MakeRequest(req *http.Request, db *mongo.Client) error {
|
||||
if va.OauthCredentials.ExpiresAt.Before(time.Now()) {
|
||||
va.OauthCredentials.RefreshAccessToken(va.Name)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@ package db
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/config"
|
||||
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/db/models"
|
||||
|
@ -31,3 +34,22 @@ func (db *DB) FindVendorAccountByUser(userId primitive.ObjectID) ([]models.Vendo
|
|||
|
||||
return vendors, nil
|
||||
}
|
||||
|
||||
//Make
|
||||
func (db *DB) MakeRequestWithAccount(req *http.Request, va *models.VendorAccount) (*http.Response, error) {
|
||||
//make new credential and save new credentials to DB
|
||||
if va.OauthCredentials.ExpiresAt.Before(time.Now()) {
|
||||
err := va.OauthCredentials.RefreshAccessToken(va.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = db.SaveModel(va)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
client := http.Client{}
|
||||
req.Header.Add("Authorization", fmt.Sprintf("%s: %s", va.OauthCredentials.TokenType, va.OauthCredentials.AccessToken))
|
||||
return client.Do(req)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package templates
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/db/models"
|
||||
)
|
||||
|
@ -227,9 +226,11 @@ templ DashboardVendorDropDown() {
|
|||
Youtube
|
||||
</button>
|
||||
</form>
|
||||
<a hx-post="/vendor/pco/initiate" href="" class="text-sm py-2 px-4 font-normal block w-full whitespace-nowrap bg-transparent text-blueGray-700">
|
||||
PCO
|
||||
</a>
|
||||
<form action="/vendor/pco/initiate" method="POST">
|
||||
<button type="submit" class="text-sm align-left py-2 px-4 font-normal block w-full whitespace-nowrap bg-transparent text-blueGray-700">
|
||||
Planning Center
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -278,7 +279,7 @@ templ DashboardVendorWidget(vendors []models.VendorAccount) {
|
|||
{ vendor.Name }
|
||||
</th>
|
||||
<th class="border-t-0 px-6 align-middle border-l-0 border-r-0 text-xs whitespace-nowrap p-4 text-left">
|
||||
if vendor.OauthCredentials != nil && vendor.OauthCredentials.AccessToken != "" && vendor.OauthCredentials.ExpiresAt.Before(time.Now()) {
|
||||
if vendor.OauthCredentials != nil && vendor.OauthCredentials.AccessToken != "" {
|
||||
Active
|
||||
} else {
|
||||
<button>Log in</button>
|
||||
|
|
Loading…
Reference in New Issue