From 6cbc2078cfe79ebd6ee744662091798cf49bcb32 Mon Sep 17 00:00:00 2001 From: Salaton Date: Tue, 17 Jan 2023 14:30:32 +0300 Subject: [PATCH] feat: introspect access token --- client.go | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 6 ++++- go.sum | 15 +++++++++++ models.go | 25 ++++++++++--------- utils.go | 2 +- 5 files changed, 109 insertions(+), 13 deletions(-) diff --git a/client.go b/client.go index f39e2fe..2bfd303 100644 --- a/client.go +++ b/client.go @@ -1,6 +1,8 @@ package authutils import ( + "bytes" + "context" "encoding/json" "fmt" "io" @@ -10,6 +12,7 @@ import ( "time" "github.com/go-playground/validator" + "moul.io/http2curl" ) // Client bundles data needed by methods in order to interact with the casdoor API @@ -101,3 +104,74 @@ func (c *Client) Authenticate() error { return nil } + +// VerifyAccessToken is used to introspect a token to determine the active state of the +// OAuth 2.0 access token and to determine meta-information about this token. +func (c *Client) VerifyAccessToken(ctx context.Context, accessToken string) (*TokenIntrospectionResponse, error) { + if accessToken == "" { + return nil, fmt.Errorf("unable to get access token from the input") + } + + introspectionURL := fmt.Sprintf("%s/v1/app/introspect/", c.configurations.AuthServerEndpoint) + payload := TokenIntrospectionPayload{ + TokenType: "access_token", + Token: accessToken, + } + + response, err := c.makeRequest(ctx, http.MethodPost, introspectionURL, payload, "application/json") + if err != nil { + return nil, err + } + + resp, err := io.ReadAll(response.Body) + if err != nil { + return nil, err + } + + var introspectionResponse *TokenIntrospectionResponse + err = json.Unmarshal(resp, &introspectionResponse) + if err != nil { + return nil, err + } + + if !introspectionResponse.IsValid { + return nil, fmt.Errorf("the supplied access token is invalid") + } + + return introspectionResponse, nil +} + +// makeRequest is a helper function for making http requests +func (c *Client) makeRequest( + ctx context.Context, + method string, + path string, + body interface{}, + contentType string, +) (*http.Response, error) { + client := http.Client{} + + encoded, err := json.Marshal(body) + if err != nil { + return nil, err + } + + payload := bytes.NewBuffer(encoded) + req, err := http.NewRequestWithContext(ctx, method, path, payload) + if err != nil { + return nil, err + } + + req.Header.Set("Accept", "application/json") + req.Header.Set("Content-Type", contentType) + + command, _ := http2curl.GetCurlCommand(req) + fmt.Println(command) + + response, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("an error occurred while sending a HTTP request: %w", err) + } + + return response, nil +} diff --git a/go.mod b/go.mod index a99e4d5..2ca141d 100644 --- a/go.mod +++ b/go.mod @@ -2,13 +2,17 @@ module github.com/savannahghi/authutils go 1.18 -require github.com/go-playground/validator v9.31.0+incompatible +require ( + github.com/go-playground/validator v9.31.0+incompatible + moul.io/http2curl v1.0.0 +) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect + github.com/smartystreets/goconvey v1.7.2 // indirect github.com/stretchr/testify v1.7.0 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect diff --git a/go.sum b/go.sum index 29e5b1b..49192c7 100644 --- a/go.sum +++ b/go.sum @@ -8,19 +8,34 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= +github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8= +moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE= diff --git a/models.go b/models.go index 4716e08..572b4f5 100644 --- a/models.go +++ b/models.go @@ -1,5 +1,7 @@ package authutils +import "time" + // LoginResponse defines the object returned when a user successfully logs in type LoginResponse struct { Scope string `json:"scope"` @@ -26,17 +28,12 @@ type CasdoorErrorResponse struct { // TokenIntrospectionResponse defines the JSON object returned by the CASDOOR service during token introspection type TokenIntrospectionResponse struct { - Active bool `json:"active"` - ClientID string `json:"client_id"` - Username string `json:"username"` - TokenType string `json:"token_type"` - Exp int `json:"exp"` - Iat int `json:"iat"` - Nbf int `json:"nbf"` - Sub string `json:"sub"` - Aud []string `json:"aud"` - Iss string `json:"iss"` - Jti string `json:"jti"` + ClientID string `json:"client_id"` + Expires time.Time `json:"expires"` + IsValid bool `json:"is_valid"` + Scope string `json:"scope"` + Token string `json:"token"` + UserGUID string `json:"user_guid"` } // Response defines the JSON object returned by most casdoor APIs @@ -48,3 +45,9 @@ type Response struct { Data interface{} `json:"data"` Data2 interface{} `json:"data2"` } + +// TokenIntrospectionPayload defines the json object passed when introspecting a token +type TokenIntrospectionPayload struct { + TokenType string `json:"token_type"` + Token string `json:"token"` +} diff --git a/utils.go b/utils.go index 24e631c..bd13761 100644 --- a/utils.go +++ b/utils.go @@ -25,5 +25,5 @@ func GetLoggedInUserUID(ctx context.Context) (string, error) { return "", fmt.Errorf("wrong auth token type, got %v", token) } - return token.Sub, nil + return token.UserGUID, nil }