-
-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): core functions to interact with OIDC APIs (#7)
- Loading branch information
Showing
9 changed files
with
357 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package core | ||
|
||
import ( | ||
"net/http" | ||
"net/url" | ||
) | ||
|
||
type FetchTokenByAuthorizationCodeOptions struct { | ||
tokenEndpoint string | ||
code string | ||
codeVerifier string | ||
clientId string | ||
redirectUri string | ||
resource string | ||
} | ||
|
||
func FetchTokenByAuthorizationCode(client *http.Client, options *FetchTokenByAuthorizationCodeOptions) (CodeTokenResponse, error) { | ||
values := url.Values{ | ||
"client_id": {options.clientId}, | ||
"redirect_uri": {options.redirectUri}, | ||
"code_verifier": {options.codeVerifier}, | ||
"code": {options.code}, | ||
"grant_type": {"authorization_code"}, | ||
} | ||
|
||
if options.resource != "" { | ||
values.Add("resource", options.resource) | ||
} | ||
|
||
response, requestErr := client.PostForm(options.tokenEndpoint, values) | ||
|
||
if requestErr != nil { | ||
return CodeTokenResponse{}, requestErr | ||
} | ||
|
||
defer response.Body.Close() | ||
|
||
var codeTokenResponse CodeTokenResponse | ||
err := parseDataFromResponse(response, &codeTokenResponse) | ||
|
||
if err != nil { | ||
return CodeTokenResponse{}, err | ||
} | ||
|
||
return codeTokenResponse, nil | ||
} | ||
|
||
type FetchTokenByRefreshTokenOptions struct { | ||
tokenEndpoint string | ||
clientId string | ||
refreshToken string | ||
resource string | ||
scope string | ||
} | ||
|
||
func FetchTokenByRefreshToken(client *http.Client, options *FetchTokenByRefreshTokenOptions) (RefreshTokenResponse, error) { | ||
values := url.Values{ | ||
"client_id": {options.clientId}, | ||
"refresh_token": {options.refreshToken}, | ||
"grant_type": {"refresh_token"}, | ||
} | ||
|
||
if options.resource != "" { | ||
values.Add("resource", options.resource) | ||
} | ||
|
||
if options.scope != "" { | ||
values.Add("scope", options.scope) | ||
} | ||
|
||
response, requestErr := client.PostForm(options.tokenEndpoint, values) | ||
|
||
if requestErr != nil { | ||
return RefreshTokenResponse{}, requestErr | ||
} | ||
|
||
defer response.Body.Close() | ||
|
||
var refreshTokenResponse RefreshTokenResponse | ||
err := parseDataFromResponse(response, &refreshTokenResponse) | ||
|
||
if err != nil { | ||
return RefreshTokenResponse{}, err | ||
} | ||
|
||
return refreshTokenResponse, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package core | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/jarcoal/httpmock" | ||
) | ||
|
||
func TestFetchTokenByAuthorizationCode(t *testing.T) { | ||
httpmock.Activate() | ||
defer httpmock.DeactivateAndReset() | ||
|
||
tokenEndpoint := "http://example.com/oidc/token" | ||
mockResponse := `{` + | ||
`"access_token": "access_token",` + | ||
`"refresh_token": "refresh_token",` + | ||
`"id_token": "id_token",` + | ||
`"scope": "openid offline_access",` + | ||
`"expires_in": 3600` + | ||
`}` | ||
|
||
httpmock.RegisterResponder( | ||
"POST", | ||
tokenEndpoint, | ||
httpmock.NewStringResponder(200, mockResponse), | ||
) | ||
|
||
client := &http.Client{} | ||
options := &FetchTokenByAuthorizationCodeOptions{ | ||
tokenEndpoint: tokenEndpoint, | ||
code: "code", | ||
codeVerifier: "codeVerifier", | ||
clientId: "clientId", | ||
redirectUri: "redirectUri", | ||
resource: "resource", | ||
} | ||
|
||
token, fetchError := FetchTokenByAuthorizationCode(client, options) | ||
if fetchError != nil { | ||
t.Fatalf(fetchError.Error()) | ||
} | ||
|
||
var expectedToken CodeTokenResponse | ||
unmarshalErr := json.Unmarshal([]byte(mockResponse), &expectedToken) | ||
|
||
if unmarshalErr != nil { | ||
t.Fatalf(unmarshalErr.Error()) | ||
} | ||
|
||
if !cmp.Equal(token, expectedToken) { | ||
t.Fatalf("token does not match expected result") | ||
} | ||
} | ||
|
||
func TestFetchTokenByRefreshToken(t *testing.T) { | ||
httpmock.Activate() | ||
defer httpmock.DeactivateAndReset() | ||
|
||
tokenEndpoint := "http://example.com/oidc/token" | ||
mockResponse := `{` + | ||
`"access_token": "access_token",` + | ||
`"refresh_token": "refresh_token",` + | ||
`"id_token": "id_token",` + | ||
`"scope": "openid offline_access",` + | ||
`"expires_in": 3600` + | ||
`}` | ||
|
||
httpmock.RegisterResponder( | ||
"POST", | ||
tokenEndpoint, | ||
httpmock.NewStringResponder(200, mockResponse), | ||
) | ||
|
||
client := &http.Client{} | ||
options := &FetchTokenByRefreshTokenOptions{ | ||
tokenEndpoint: tokenEndpoint, | ||
clientId: "clientId", | ||
refreshToken: "refresh_token", | ||
resource: "resource", | ||
scope: "openid offline_access", | ||
} | ||
|
||
token, fetchError := FetchTokenByRefreshToken(client, options) | ||
if fetchError != nil { | ||
t.Fatalf(fetchError.Error()) | ||
} | ||
|
||
var expectedToken RefreshTokenResponse | ||
unmarshalErr := json.Unmarshal([]byte(mockResponse), &expectedToken) | ||
|
||
if unmarshalErr != nil { | ||
t.Fatalf(unmarshalErr.Error()) | ||
} | ||
|
||
if !cmp.Equal(token, expectedToken) { | ||
t.Fatalf("token does not match expected result") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package core | ||
|
||
import ( | ||
"net/http" | ||
) | ||
|
||
func FetchOidcConfig(client *http.Client, endpoint string) (OidcConfigResponse, error) { | ||
response, fetchErr := client.Get(endpoint) | ||
|
||
if fetchErr != nil { | ||
return OidcConfigResponse{}, fetchErr | ||
} | ||
|
||
defer response.Body.Close() | ||
|
||
var config OidcConfigResponse | ||
err := parseDataFromResponse(response, &config) | ||
|
||
if err != nil { | ||
return OidcConfigResponse{}, err | ||
} | ||
|
||
return config, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package core | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/jarcoal/httpmock" | ||
) | ||
|
||
func TestFetchOidcConfig(t *testing.T) { | ||
httpmock.Activate() | ||
defer httpmock.DeactivateAndReset() | ||
|
||
endpoint := "http://example.com/oidc/.well-known/openid-configuration" | ||
mockResponse := `{` + | ||
`"authorization_endpoint": "http://example.com/oidc/authorize",` + | ||
`"token_endpoint": "http://example.com/oidc/token",` + | ||
`"end_session_endpoint": "http://example.com/oidc/logout",` + | ||
`"revocation_endpoint": "http://example.com/oidc/revoke",` + | ||
`"jwks_uri": "http://example.com/oidc/jwks",` + | ||
`"issuer": "http://example.com/oidc"` + | ||
`}` | ||
|
||
httpmock.RegisterResponder( | ||
"GET", | ||
endpoint, | ||
httpmock.NewStringResponder(200, mockResponse), | ||
) | ||
|
||
client := &http.Client{} | ||
config, err := FetchOidcConfig(client, endpoint) | ||
if err != nil { | ||
t.Fatalf(err.Error()) | ||
} | ||
var expectedConfig OidcConfigResponse | ||
|
||
unmarshalErr := json.Unmarshal([]byte(mockResponse), &expectedConfig) | ||
|
||
if unmarshalErr != nil { | ||
t.Fatalf(unmarshalErr.Error()) | ||
} | ||
|
||
if !cmp.Equal(config, expectedConfig) { | ||
t.Fatalf("config does not match expected result") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package core | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
) | ||
|
||
func parseDataFromResponse(response *http.Response, dest interface{}) error { | ||
defer response.Body.Close() | ||
body, err := io.ReadAll(response.Body) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if response.StatusCode != http.StatusOK { | ||
return fmt.Errorf("unexpected status code: %d, response body: %s", response.StatusCode, body) | ||
} | ||
|
||
return json.Unmarshal(body, &dest) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package core | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
) | ||
|
||
type RevocationOptions struct { | ||
revocationEndpoint string | ||
clientId string | ||
token string | ||
} | ||
|
||
func Revoke(client *http.Client, options *RevocationOptions) error { | ||
values := url.Values{ | ||
"client_id": {options.clientId}, | ||
"token": {options.token}, | ||
} | ||
response, fetchErr := client.PostForm(options.revocationEndpoint, values) | ||
|
||
if fetchErr != nil { | ||
return fetchErr | ||
} | ||
|
||
defer response.Body.Close() | ||
|
||
if response.StatusCode != http.StatusOK { | ||
return fmt.Errorf("unexpected status code: %d", response.StatusCode) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package core | ||
|
||
import ( | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/jarcoal/httpmock" | ||
) | ||
|
||
func TestRevoke(t *testing.T) { | ||
httpmock.Activate() | ||
defer httpmock.DeactivateAndReset() | ||
|
||
revocationEndpoint := "http://example.com/oidc/revoke" | ||
mockResponse := `{}` | ||
|
||
httpmock.RegisterResponder( | ||
"POST", | ||
revocationEndpoint, | ||
httpmock.NewStringResponder(200, mockResponse), | ||
) | ||
|
||
client := &http.Client{} | ||
options := &RevocationOptions{ | ||
revocationEndpoint: revocationEndpoint, | ||
clientId: "clientId", | ||
token: "token", | ||
} | ||
|
||
err := Revoke(client, options) | ||
|
||
if err != nil { | ||
t.Fatalf(err.Error()) | ||
} | ||
} |