Skip to content

Commit

Permalink
feat: Add Go implementation for Coze OAuth QuickStart (#15)
Browse files Browse the repository at this point in the history
Co-authored-by: luoyangze.ptrl <[email protected]>
Co-authored-by: chyroc <[email protected]>
  • Loading branch information
3 people authored Jan 21, 2025
1 parent 9d0c489 commit 3dc506a
Show file tree
Hide file tree
Showing 20 changed files with 897 additions and 188 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ release/
go/**/assets
go/**/websites
go/**/bootstrap.sh
!go/bootstrap.sh
go/**/bootstrap.bat
go/**/bootstrap.ps1
nodejs/**/assets
Expand Down
23 changes: 23 additions & 0 deletions go/bootstrap.sh
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
7 changes: 7 additions & 0 deletions go/device-oauth/go.mod
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
8 changes: 8 additions & 0 deletions go/device-oauth/go.sum
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=
86 changes: 86 additions & 0 deletions go/device-oauth/main.go
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)
}
4 changes: 4 additions & 0 deletions go/device-oauth/quickstart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Run this command for linux/macos users:
```bash
./bootstrap.sh
```
52 changes: 0 additions & 52 deletions go/jwt-oauth/bootstrap.sh

This file was deleted.

File renamed without changes.
File renamed without changes.
190 changes: 190 additions & 0 deletions go/jwt-oauth/main.go
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)
}
}
4 changes: 4 additions & 0 deletions go/jwt-oauth/quickstart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Run this command for linux/macos users:
```bash
./bootstrap.sh
```
Loading

0 comments on commit 3dc506a

Please sign in to comment.