Skip to content
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

feat: add basic-auth credential tool #228

Merged
merged 1 commit into from
Nov 25, 2024
Merged
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
22 changes: 22 additions & 0 deletions basic-auth/go.mod
Original file line number Diff line number Diff line change
@@ -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
)
46 changes: 46 additions & 0 deletions basic-auth/go.sum
Original file line number Diff line number Diff line change
@@ -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=
148 changes: 148 additions & 0 deletions basic-auth/main.go
Original file line number Diff line number Diff line change
@@ -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, 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, field string) (string, error) {
env = strings.TrimSpace(env)
if env == "" {
return "", fmt.Errorf("Environment variable name for field %s must be set", 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
}
9 changes: 9 additions & 0 deletions basic-auth/tool.gpt
Original file line number Diff line number Diff line change
@@ -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}"