From 6929c0a0a8f7e6b44f120fd16b4889c68310fbd9 Mon Sep 17 00:00:00 2001 From: Nick Hale <4175918+njhale@users.noreply.github.com> Date: Thu, 21 Nov 2024 20:23:38 -0500 Subject: [PATCH] feat: add basic-auth credential tool Add the `Basic Auth` tool to support collecting a username and password from a user. This is useful for interacting with platforms with no or limited OAuth support. Signed-off-by: Nick Hale <4175918+njhale@users.noreply.github.com> --- basic-auth/go.mod | 22 +++++++ basic-auth/go.sum | 46 ++++++++++++++ basic-auth/main.go | 148 ++++++++++++++++++++++++++++++++++++++++++++ basic-auth/tool.gpt | 9 +++ 4 files changed, 225 insertions(+) create mode 100644 basic-auth/go.mod create mode 100644 basic-auth/go.sum create mode 100644 basic-auth/main.go create mode 100644 basic-auth/tool.gpt diff --git a/basic-auth/go.mod b/basic-auth/go.mod new file mode 100644 index 00000000..6439f1be --- /dev/null +++ b/basic-auth/go.mod @@ -0,0 +1,22 @@ +module basic-auth + +go 1.23.3 + +require ( + github.com/gptscript-ai/go-gptscript v0.9.6-0.20241106212914-ba040ce8f47b + github.com/tidwall/gjson v1.18.0 +) + +require ( + github.com/getkin/kin-openapi v0.124.0 // indirect + github.com/go-openapi/jsonpointer v0.20.2 // indirect + github.com/go-openapi/swag v0.22.8 // indirect + github.com/invopop/yaml v0.2.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/basic-auth/go.sum b/basic-auth/go.sum new file mode 100644 index 00000000..33aa9cb3 --- /dev/null +++ b/basic-auth/go.sum @@ -0,0 +1,46 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M= +github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= +github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= +github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= +github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw= +github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/gptscript-ai/go-gptscript v0.9.6-0.20241106212914-ba040ce8f47b h1:adIh3EnMTlC19t2k1IJoOtF6me/hPxks2GxSSgB7oEw= +github.com/gptscript-ai/go-gptscript v0.9.6-0.20241106212914-ba040ce8f47b/go.mod h1:/FVuLwhz+sIfsWUgUHWKi32qT0i6+IXlUlzs70KKt/Q= +github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= +github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +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/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/basic-auth/main.go b/basic-auth/main.go new file mode 100644 index 00000000..f609e013 --- /dev/null +++ b/basic-auth/main.go @@ -0,0 +1,148 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "os/signal" + "regexp" + "strconv" + "strings" + + "github.com/gptscript-ai/go-gptscript" + "github.com/tidwall/gjson" +) + +type input struct { + ToolDisplayName string `json:"tool_display_name,omitempty"` + Message string `json:"message"` + UsernameField string `json:"username_field,omitempty"` + UsernameEnv string `json:"username_env,omitempty"` + PasswordField string `json:"password_field,omitempty"` + PasswordEnv string `json:"password_env,omitempty"` + Metadata map[string]string +} + +type sysPromptInput struct { + Message string `json:"message,omitempty"` + Fields string `json:"fields,omitempty"` + Sensitive string `json:"sensitive,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` +} + +func main() { + // Set up signal handler + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) + defer cancel() + + in, err := getInput() + if err != nil { + fmt.Println("Input error: ", err) + os.Exit(1) + } + + username, password, err := getCredentials(ctx, in) + if err != nil { + fmt.Println("Error getting credentials:", err) + os.Exit(1) + } + fmt.Printf(`{"env": {"%s": "%s", "%s": "%s"}}`, in.UsernameEnv, username, in.PasswordEnv, password) +} + +func getCredentials(ctx context.Context, in input) (string, string, error) { + client, err := gptscript.NewGPTScript() + if err != nil { + fmt.Println("Error creating GPTScript client:", err) + return "", "", fmt.Errorf("Error creating GPTScript client: %w", err) + } + defer client.Close() + + sysPromptIn, err := json.Marshal(sysPromptInput{ + Message: in.Message, + Fields: strings.Join([]string{in.UsernameField, in.PasswordField}, ","), + Sensitive: strconv.FormatBool(true), + Metadata: in.Metadata, + }) + if err != nil { + return "", "", fmt.Errorf("Error marshalling sys prompt input: %w", err) + } + + run, err := client.Run(ctx, "sys.prompt", gptscript.Options{ + Input: string(sysPromptIn), + }) + if err != nil { + return "", "", fmt.Errorf("Error running GPTScript prompt: %w", err) + } + + res, err := run.Text() + if err != nil { + return "", "", fmt.Errorf("Error getting GPTScript response: %w", err) + } + + username := gjson.Get(res, in.UsernameField).String() + password := gjson.Get(res, in.PasswordField).String() + + return username, password, nil + +} + +func getInput() (input, error) { + if len(os.Args) != 2 { + return input{}, errors.New("Missing input string") + } + + inputStr := os.Args[1] + + var in input + if err := json.Unmarshal([]byte(inputStr), &in); err != nil { + return input{}, fmt.Errorf("Error parsing input JSON: %w", err) + } + + // Helper function to trim and set default values + cleanField := func(field string, defaultValue string) string { + field = strings.TrimSpace(field) + if field == "" { + return defaultValue + } + return field + } + + in.ToolDisplayName = cleanField(in.ToolDisplayName, "Basic Auth Credential") + in.UsernameField = cleanField(in.UsernameField, "username") + in.PasswordField = cleanField(in.PasswordField, "password") + + // Set environment variables and validate + var validEnvPattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) + cleanEnv := func(env string, field string) (string, error) { + env = strings.TrimSpace(env) + if env == "" { + env = strings.ToUpper(field) + } + if !validEnvPattern.MatchString(env) { + return "", fmt.Errorf("Invalid environment variable name: %s", env) + } + return env, nil + } + + var err error + in.UsernameEnv, err = cleanEnv(in.UsernameEnv, in.UsernameField) + if err != nil { + return input{}, err + } + in.PasswordEnv, err = cleanEnv(in.PasswordEnv, in.PasswordField) + if err != nil { + return input{}, err + } + + in.Message = fmt.Sprintf("Enter your %s and %s", in.UsernameField, in.PasswordField) + + in.Metadata = map[string]string{ + "authType": "basic", + "toolContext": "credential", + "toolDisplayName": in.ToolDisplayName, + } + + return in, nil +} diff --git a/basic-auth/tool.gpt b/basic-auth/tool.gpt new file mode 100644 index 00000000..62e428b1 --- /dev/null +++ b/basic-auth/tool.gpt @@ -0,0 +1,9 @@ +Name: basic-auth +Param: tool_display_name: The tool name to display to the user when prompting for credentials +Param: message: The message to display the when prompting for this credential +Param: username_field: The name of the username field +Param: username_env: The name of the environment variable to set for the captured username +Param: password_field: The name of the password field +Param: password_env: The name of the environment variable to set for the captured password + +#!${GPTSCRIPT_TOOL_DIR}/bin/gptscript-go-tool "${GPTSCRIPT_INPUT}" \ No newline at end of file