Skip to content

feat: oauth feature #267

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"strings"
"time"

"github.com/twilio/twilio-go/oauth"

"github.com/pkg/errors"
"github.com/twilio/twilio-go/client/form"
)
Comment on lines +16 to 20

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: import ordering.

Expand All @@ -27,8 +29,34 @@ func init() {

// Credentials store user authentication credentials.
type Credentials struct {
Username string
Password string
Username string
Password string
ClientCredentials *ClientCredentials
}

type ClientCredentials struct {
GrantType string
ClientId string
ClientSecret string
RequestHandler RequestHandler
}

func NewClientCredentials(grantType, clientId, clientSecret string, handler RequestHandler) *ClientCredentials {
return &ClientCredentials{GrantType: grantType, ClientId: clientId, ClientSecret: clientSecret, RequestHandler: handler}
}
Comment on lines +44 to +46
Copy link

@light-bringer light-bringer Mar 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: not much readable, can we try running a linter (preferably go-fumpt)


func (c *ClientCredentials) GetAccessToken() (string, error) {
tokenManager := &oauth.TokenManager{
GrantType: c.GrantType,
ClientId: c.ClientId,
ClientSecret: c.ClientSecret,
Code: "",
Audience: "",
RefreshToken: "",
Scope: "",
}
tokenAuth := oauth.NewTokenAuthInitializer("", tokenManager)
return tokenAuth.FetchToken(c.RequestHandler)
}

func NewCredentials(username string, password string) *Credentials {
Expand All @@ -41,6 +69,7 @@ type Client struct {
HTTPClient *http.Client
accountSid string
UserAgentExtensions []string
ClientCredentials *ClientCredentials
}

// default http Client should not follow redirects and return the most recent response.
Expand All @@ -65,6 +94,10 @@ func (c *Client) SetTimeout(timeout time.Duration) {
c.HTTPClient.Timeout = timeout
}

func (c *Client) SetClientCredentials(clientCredentials *ClientCredentials) {
c.ClientCredentials = clientCredentials
}

func extractContentTypeHeader(headers map[string]interface{}) (cType string) {
headerType, ok := headers["Content-Type"]
if !ok {
Expand Down Expand Up @@ -186,6 +219,14 @@ func (c *Client) SendRequest(method string, rawURL string, data url.Values,
if len(c.UserAgentExtensions) > 0 {
userAgent += " " + strings.Join(c.UserAgentExtensions, " ")
}
if c.ClientCredentials != nil {
token, err := c.ClientCredentials.GetAccessToken()
if err != nil {
req.Header.Add("Authorization", "Bearer "+token)
}
} else if c.Username != "" {
req.SetBasicAuth(c.basicAuth())
}

req.Header.Add("User-Agent", userAgent)

Expand Down
62 changes: 62 additions & 0 deletions oauth/token_auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package oauth

import (
"time"

"github.com/golang-jwt/jwt"
"github.com/twilio/twilio-go/client"
)

type TokenAuth struct {
token string
tokenManager *TokenManager
}

func NewTokenAuthInitializer(token string, tokenManager *TokenManager) *TokenAuth {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can rename NewTokenAuthInitializer to NewTokenAuth. Any func prefixed with New is already considered as a constructor func

return &TokenAuth{
token: token,
tokenManager: tokenManager,
}
}
func NewTokenAuth(grantType, clientId, clientSecret, code, audience, refreshToken, scope string) *TokenAuth {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, this is not being used anywhere.

return &TokenAuth{
tokenManager: &TokenManager{
GrantType: grantType,
ClientId: clientId,
ClientSecret: clientSecret,
Code: code,
Audience: audience,
RefreshToken: refreshToken,
Scope: scope,
},
}
}

func (t *TokenAuth) FetchToken(c client.RequestHandler) (string, error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good to have func level comment to get quick glimpse of what func does and also helps when func is referenced in other packages.

if t.token != "" && !t.TokenExpired() {
return t.token, nil
}

token, err := t.tokenManager.fetchAccessToken(c)
if err != nil {
return "", err
}

t.token = token
return t.token, nil
}

func (t *TokenAuth) TokenExpired() bool {
token, _, err := new(jwt.Parser).ParseUnverified(t.token, jwt.MapClaims{})
if err != nil {
return true
}

if claims, ok := token.Claims.(jwt.MapClaims); ok {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to catch err here if token.Claims do not assert to jwt.MapClaims ? in what case that can happen, need more info here

if exp, ok := claims["exp"].(float64); ok {
expirationTime := time.Unix(int64(exp), 0)
return time.Now().After(expirationTime)
}
}
return true
}
52 changes: 52 additions & 0 deletions oauth/token_manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package oauth

import (
"fmt"

"github.com/twilio/twilio-go/client"
preview_iam "github.com/twilio/twilio-go/rest/preview_iam/v1"
)

type TokenManager struct {
GrantType string
ClientId string
ClientSecret string
Code string
Audience string
RefreshToken string
Scope string
}

func NewTokenManager(grantType, clientId, clientSecret, code, audience, refreshToken, scope string) *TokenManager {
return &TokenManager{
GrantType: grantType,
ClientId: clientId,
ClientSecret: clientSecret,
Code: code,
Audience: audience,
RefreshToken: refreshToken,
Scope: scope,
}
}

func (tm *TokenManager) fetchAccessToken(c client.RequestHandler) (string, error) {
params := &preview_iam.CreateTokenParams{}
params.SetGrantType(tm.GrantType).
SetClientId(tm.ClientId).
SetClientSecret(tm.ClientSecret).
SetCode(tm.Code).
SetAudience(tm.Audience).
SetRefreshToken(tm.RefreshToken).
SetScope(tm.Scope)

token, err := preview_iam.NewApiService(&c).CreateToken(params)
if err != nil {
return "", err
}

if token.AccessToken == nil {
return "", fmt.Errorf("access token is nil")
}

return *token.AccessToken, nil
}
94 changes: 94 additions & 0 deletions rest/preview_iam/v1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Go API client for openapi

No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)

## Overview
This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project from the OpenAPI specs located at [twilio/twilio-oai](https://github.com/twilio/twilio-oai/tree/main/spec). By using the [OpenAPI-spec](https://www.openapis.org/) from a remote server, you can easily generate an API client.

- API version: 1.0.0
- Package version: 1.0.0
- Build package: com.twilio.oai.TwilioGoGenerator
For more information, please visit [https://support.twilio.com](https://support.twilio.com)

## Installation

Install the following dependencies:

```shell
go get github.com/stretchr/testify/assert
go get golang.org/x/net/context
```

Put the package under your project folder and add the following in import:

```golang
import "./openapi"
```

## Documentation for API Endpoints

All URIs are relative to *https://preview-iam.twilio.com*

Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
*AuthorizeApi* | [**FetchAuthorize**](docs/AuthorizeApi.md#fetchauthorize) | **Get** /v1/authorize | Retrieves authorize uri
*TokenApi* | [**CreateToken**](docs/TokenApi.md#createtoken) | **Post** /v1/token | Issues a new Access token (optionally identity_token & refresh_token) in exchange of Oauth grant


## Documentation For Models

- [ScimError](docs/ScimError.md)
- [ScimUser](docs/ScimUser.md)
- [PublicApiCreateRoleAssignmentRequest](docs/PublicApiCreateRoleAssignmentRequest.md)
- [ScimMeta](docs/ScimMeta.md)
- [ScimName](docs/ScimName.md)
- [PublicApiRoleAssignmentResponse](docs/PublicApiRoleAssignmentResponse.md)
- [ScimResourceTypes](docs/ScimResourceTypes.md)
- [OauthV1Authorize](docs/OauthV1Authorize.md)
- [PublicApiCreateAccountResponse](docs/PublicApiCreateAccountResponse.md)
- [OauthV1Token](docs/OauthV1Token.md)
- [PublicApiCreateAccountRequest](docs/PublicApiCreateAccountRequest.md)
- [TwilioServiceErrorResponse](docs/TwilioServiceErrorResponse.md)
- [PublicApiAccountResponsePage](docs/PublicApiAccountResponsePage.md)
- [ScimPatchRequest](docs/ScimPatchRequest.md)
- [PublicApiCreateRoleAssignmentResponsePage](docs/PublicApiCreateRoleAssignmentResponsePage.md)
- [ScimUserPage](docs/ScimUserPage.md)
- [JsonPatch](docs/JsonPatch.md)
- [ScimEmailAddress](docs/ScimEmailAddress.md)
- [PublicApiAccountResponsePageMeta](docs/PublicApiAccountResponsePageMeta.md)
- [PublicApiAccountResponse](docs/PublicApiAccountResponse.md)
- [ScimPatchOperation](docs/ScimPatchOperation.md)
- [ScimResourceTypesResources](docs/ScimResourceTypesResources.md)


## Documentation For Authorization



## oAuth2ClientCredentials


- **Type**: OAuth
- **Flow**: application
- **Authorization URL**:
- **Scopes**: N/A

Example

```golang
auth := context.WithValue(context.Background(), sw.ContextAccessToken, "ACCESSTOKENSTRING")
r, err := client.Service.Operation(auth, args)
```

Or via OAuth2 module to automatically refresh tokens and perform user authentication.

```golang
import "golang.org/x/oauth2"

/* Perform OAuth2 round trip request and obtain a token */

tokenSource := oauth2cfg.TokenSource(createContext(httpClient), &token)
auth := context.WithValue(oauth2.NoContext, sw.ContextOAuth2, tokenSource)
r, err := client.Service.Operation(auth, args)
```

35 changes: 35 additions & 0 deletions rest/preview_iam/v1/api_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* This code was generated by
* ___ _ _ _ _ _ _ ____ ____ ____ _ ____ ____ _ _ ____ ____ ____ ___ __ __
* | | | | | | | | | __ | | |__| | __ | __ |___ |\ | |___ |__/ |__| | | | |__/
* | |_|_| | |___ | |__| |__| | | | |__] |___ | \| |___ | \ | | | |__| | \
*
* Organization Public API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* NOTE: This class is auto generated by OpenAPI Generator.
* https://openapi-generator.tech
* Do not edit the class manually.
*/

package openapi

import (
twilio "github.com/twilio/twilio-go/client"
)

type ApiService struct {
baseURL string
requestHandler *twilio.RequestHandler
}

func NewApiService(requestHandler *twilio.RequestHandler) *ApiService {
return &ApiService{
requestHandler: requestHandler,
baseURL: "https://preview-iam.twilio.com",
}
}

func NewApiServiceWithClient(client twilio.BaseClient) *ApiService {
return NewApiService(twilio.NewRequestHandler(client))
}
Loading
Loading