-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add Go implementation for Coze OAuth QuickStart (#15)
Co-authored-by: luoyangze.ptrl <[email protected]> Co-authored-by: chyroc <[email protected]>
- Loading branch information
1 parent
9d0c489
commit 3dc506a
Showing
20 changed files
with
897 additions
and
188 deletions.
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
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,23 @@ | ||
#!/bin/bash | ||
# Check if Go is installed | ||
if ! command -v go &> /dev/null; then | ||
echo "Error: Go is not installed on your system." | ||
echo "To install Go, please visit: https://golang.org/doc/install" | ||
echo "After installation, make sure 'go' command is available in your PATH" | ||
exit 1 | ||
fi | ||
|
||
# Print Go version | ||
echo "Using Go version: $(go version)" | ||
|
||
# Change to src directory and build | ||
# Build the binary | ||
echo "Building application..." | ||
go build -o build/main main.go || { | ||
echo "Error: Failed to build the application" | ||
exit 1 | ||
} | ||
|
||
# Run the binary | ||
echo "Starting the application..." | ||
./build/main |
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,7 @@ | ||
module github.com/coze-dev/coze-oauth-quickstart/go/device-oauth | ||
|
||
go 1.18 | ||
|
||
require github.com/coze-dev/coze-go v0.0.0-20250115094839-23bd27bd6561 | ||
|
||
require github.com/golang-jwt/jwt v3.2.2+incompatible // indirect |
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,8 @@ | ||
github.com/coze-dev/coze-go v0.0.0-20250115094839-23bd27bd6561 h1:LRZX2wQJEZ8VWuZipbhfiPJdirtKv7t840rIUVnLoSA= | ||
github.com/coze-dev/coze-go v0.0.0-20250115094839-23bd27bd6561/go.mod h1:zc7+Lrzh5Lm7BYmqbtjMnV3eoRJpjE05LSqtT+iH+LE= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= | ||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= |
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,86 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"log" | ||
"os" | ||
"time" | ||
|
||
"github.com/coze-dev/coze-go" | ||
) | ||
|
||
const CozeOAuthConfigPath = "coze_oauth_config.json" | ||
|
||
type Config struct { | ||
ClientType string `json:"client_type"` | ||
ClientID string `json:"client_id"` | ||
CozeDomain string `json:"coze_www_base"` | ||
CozeAPIBase string `json:"coze_api_base"` | ||
} | ||
|
||
func loadConfig() (*Config, error) { | ||
configFile, err := os.ReadFile(CozeOAuthConfigPath) | ||
if err != nil { | ||
if os.IsNotExist(err) { | ||
return nil, fmt.Errorf("coze_oauth_config.json not found in current directory") | ||
} | ||
return nil, fmt.Errorf("failed to read config file: %v", err) | ||
} | ||
|
||
var config Config | ||
if err := json.Unmarshal(configFile, &config); err != nil { | ||
return nil, fmt.Errorf("failed to parse config file: %v", err) | ||
} | ||
|
||
if config.ClientType != "device" { | ||
return nil, fmt.Errorf("invalid client type: %s. expected: device", config.ClientType) | ||
} | ||
|
||
return &config, nil | ||
} | ||
|
||
func timestampToDateTime(timestamp int64) string { | ||
t := time.Unix(timestamp, 0) | ||
return t.Format("2006-01-02 15:04:05") | ||
} | ||
|
||
func main() { | ||
log.SetFlags(0) | ||
|
||
config, err := loadConfig() | ||
if err != nil { | ||
log.Fatalf("Error loading config: %v", err) | ||
} | ||
|
||
oauth, err := coze.NewDeviceOAuthClient( | ||
config.ClientID, | ||
coze.WithAuthBaseURL(config.CozeAPIBase), | ||
) | ||
if err != nil { | ||
log.Fatalf("Error creating device OAuth client: %v\n", err) | ||
} | ||
|
||
ctx := context.Background() | ||
deviceCode, err := oauth.GetDeviceCode(ctx, &coze.GetDeviceOAuthCodeReq{}) | ||
if err != nil { | ||
log.Fatalf("Error getting device code: %v\n", err) | ||
} | ||
|
||
fmt.Println("Please visit the following url to authorize the app:") | ||
fmt.Printf(" URL: %s\n\n", deviceCode.VerificationURL) | ||
|
||
resp, err := oauth.GetAccessToken(ctx, &coze.GetDeviceOAuthAccessTokenReq{ | ||
DeviceCode: deviceCode.DeviceCode, | ||
Poll: true, | ||
}) | ||
if err != nil { | ||
log.Fatalf("Error getting access token: %v\n", err) | ||
} | ||
|
||
fmt.Printf("[device-oauth] access_token: %s\n", resp.AccessToken) | ||
fmt.Printf("[device-oauth] refresh_token: %s\n", resp.RefreshToken) | ||
expiresStr := timestampToDateTime(resp.ExpiresIn) | ||
fmt.Printf("[device-oauth] expires_in: %d (%s)\n", resp.ExpiresIn, expiresStr) | ||
} |
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,4 @@ | ||
Run this command for linux/macos users: | ||
```bash | ||
./bootstrap.sh | ||
``` |
This file was deleted.
Oops, something went wrong.
File renamed without changes.
File renamed without changes.
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,190 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"log" | ||
"net" | ||
"net/http" | ||
"os" | ||
"strings" | ||
"time" | ||
|
||
"github.com/coze-dev/coze-go" | ||
) | ||
|
||
const CozeOAuthConfigPath = "coze_oauth_config.json" | ||
|
||
type Config struct { | ||
ClientType string `json:"client_type"` | ||
ClientID string `json:"client_id"` | ||
PrivateKey string `json:"private_key"` | ||
PublicKeyID string `json:"public_key_id"` | ||
CozeDomain string `json:"coze_www_base"` | ||
CozeAPIBase string `json:"coze_api_base"` | ||
} | ||
|
||
type TokenResponse struct { | ||
TokenType string `json:"token_type"` | ||
AccessToken string `json:"access_token"` | ||
RefreshToken string `json:"refresh_token"` | ||
ExpiresIn string `json:"expires_in"` | ||
} | ||
|
||
func loadConfig() (*Config, error) { | ||
configFile, err := os.ReadFile(CozeOAuthConfigPath) | ||
if err != nil { | ||
if os.IsNotExist(err) { | ||
return nil, fmt.Errorf("coze_oauth_config.json not found in current directory") | ||
} | ||
return nil, fmt.Errorf("failed to read config file: %v", err) | ||
} | ||
|
||
var config Config | ||
if err := json.Unmarshal(configFile, &config); err != nil { | ||
return nil, fmt.Errorf("failed to parse config file: %v", err) | ||
} | ||
|
||
if config.ClientType != "jwt" { | ||
return nil, fmt.Errorf("invalid client type: %s. expected: jwt", config.ClientType) | ||
} | ||
|
||
return &config, nil | ||
} | ||
|
||
func timestampToDateTime(timestamp int64) string { | ||
t := time.Unix(timestamp, 0) | ||
return t.Format("2006-01-02 15:04:05") | ||
} | ||
|
||
func readHTMLTemplate(filePath string) (string, error) { | ||
content, err := ioutil.ReadFile(filePath) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to read template file: %v", err) | ||
} | ||
return string(content), nil | ||
} | ||
|
||
func renderTemplate(template string, data map[string]interface{}) string { | ||
result := template | ||
for key, value := range data { | ||
placeholder := "{{" + key + "}}" | ||
result = strings.ReplaceAll(result, placeholder, fmt.Sprintf("%v", value)) | ||
} | ||
return result | ||
} | ||
|
||
func main() { | ||
log.SetFlags(0) | ||
|
||
config, err := loadConfig() | ||
if err != nil { | ||
log.Fatalf("Error loading config: %v", err) | ||
} | ||
|
||
oauth, err := coze.NewJWTOAuthClient(coze.NewJWTOAuthClientParam{ | ||
ClientID: config.ClientID, | ||
PublicKey: config.PublicKeyID, | ||
PrivateKeyPEM: config.PrivateKey, | ||
}, coze.WithAuthBaseURL(config.CozeAPIBase)) | ||
if err != nil { | ||
log.Fatalf("Error creating JWT OAuth client: %v\n", err) | ||
} | ||
|
||
listener, err := net.Listen("tcp", "127.0.0.1:8080") | ||
if err != nil { | ||
log.Fatalf("Port 8080 is already in use by another application. Please free up the port and try again") | ||
} | ||
listener.Close() | ||
|
||
fs := http.FileServer(http.Dir("assets")) | ||
http.Handle("/assets/", http.StripPrefix("/assets/", fs)) | ||
|
||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | ||
if r.URL.Path != "/" { | ||
http.NotFound(w, r) | ||
return | ||
} | ||
|
||
template, err := readHTMLTemplate("websites/index.html") | ||
if err != nil { | ||
http.Error(w, "Internal Server Error", http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
data := map[string]interface{}{ | ||
"client_type": config.ClientType, | ||
"client_id": config.ClientID, | ||
"coze_www_base": config.CozeDomain, | ||
} | ||
|
||
result := renderTemplate(template, data) | ||
w.Write([]byte(result)) | ||
}) | ||
|
||
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { | ||
http.Redirect(w, r, "/callback", http.StatusFound) | ||
}) | ||
|
||
http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) { | ||
ctx := context.Background() | ||
resp, err := oauth.GetAccessToken(ctx, nil) | ||
if err != nil { | ||
template, parseErr := readHTMLTemplate("websites/error.html") | ||
if parseErr != nil { | ||
http.Error(w, "Internal Server Error", http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
data := map[string]interface{}{ | ||
"error": fmt.Sprintf("Failed to get access token: %v", err), | ||
"coze_www_base": config.CozeDomain, | ||
} | ||
|
||
w.WriteHeader(http.StatusInternalServerError) | ||
result := renderTemplate(template, data) | ||
w.Write([]byte(result)) | ||
return | ||
} | ||
|
||
expiresStr := fmt.Sprintf("%d (%s)", resp.ExpiresIn, timestampToDateTime(resp.ExpiresIn)) | ||
tokenResp := TokenResponse{ | ||
TokenType: "Bearer", | ||
AccessToken: resp.AccessToken, | ||
RefreshToken: "", | ||
ExpiresIn: expiresStr, | ||
} | ||
|
||
// Check if it's an AJAX request | ||
if r.Header.Get("X-Requested-With") == "XMLHttpRequest" { | ||
w.Header().Set("Content-Type", "application/json") | ||
json.NewEncoder(w).Encode(tokenResp) | ||
return | ||
} | ||
|
||
// Otherwise render the callback template | ||
template, err := readHTMLTemplate("websites/callback.html") | ||
if err != nil { | ||
http.Error(w, "Internal Server Error", http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
data := map[string]interface{}{ | ||
"token_type": tokenResp.TokenType, | ||
"access_token": tokenResp.AccessToken, | ||
"refresh_token": tokenResp.RefreshToken, | ||
"expires_in": tokenResp.ExpiresIn, | ||
} | ||
|
||
result := renderTemplate(template, data) | ||
w.Write([]byte(result)) | ||
}) | ||
|
||
log.Printf("\nServer starting on http://127.0.0.1:8080 (API Base: %s, Client Type: %s, Client ID: %s)\n", | ||
config.CozeAPIBase, config.ClientType, config.ClientID) | ||
if err := http.ListenAndServe("127.0.0.1:8080", nil); err != nil { | ||
log.Fatalf("Server failed to start: %v", err) | ||
} | ||
} |
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,4 @@ | ||
Run this command for linux/macos users: | ||
```bash | ||
./bootstrap.sh | ||
``` |
Oops, something went wrong.