feat(backend): add kiro account support
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
package kiro
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type RuntimeFingerprint struct {
|
||||
OIDCSDKVersion string
|
||||
RuntimeSDKVersion string
|
||||
StreamingSDKVersion string
|
||||
OSType string
|
||||
OSVersion string
|
||||
NodeVersion string
|
||||
KiroVersion string
|
||||
KiroHash string
|
||||
}
|
||||
|
||||
type runtimeFingerprintManager struct {
|
||||
mu sync.RWMutex
|
||||
fingerprints map[string]*RuntimeFingerprint
|
||||
}
|
||||
|
||||
var (
|
||||
globalRuntimeFingerprintManager *runtimeFingerprintManager
|
||||
globalRuntimeFingerprintManagerOnce sync.Once
|
||||
|
||||
oidcSDKVersions = []string{"3.980.0", "3.975.0", "3.972.0", "3.808.0", "3.738.0", "3.737.0", "3.736.0", "3.735.0"}
|
||||
runtimeSDKVersions = []string{"1.0.0"}
|
||||
streamingSDKVersions = []string{"1.0.34"}
|
||||
osTypes = []string{"darwin", "win32"}
|
||||
osVersions = map[string][]string{
|
||||
"darwin": {"24.6.0"},
|
||||
"win32": {"10.0.22631"},
|
||||
}
|
||||
nodeVersions = []string{"22.22.0"}
|
||||
kiroVersions = []string{
|
||||
"0.11.132", "0.11.131", "0.11.130",
|
||||
}
|
||||
)
|
||||
|
||||
func globalRuntimeFingerprints() *runtimeFingerprintManager {
|
||||
globalRuntimeFingerprintManagerOnce.Do(func() {
|
||||
globalRuntimeFingerprintManager = &runtimeFingerprintManager{
|
||||
fingerprints: make(map[string]*RuntimeFingerprint),
|
||||
}
|
||||
})
|
||||
return globalRuntimeFingerprintManager
|
||||
}
|
||||
|
||||
func (m *runtimeFingerprintManager) Get(accountKey, machineID string) *RuntimeFingerprint {
|
||||
lookupKey := fingerprintLookupKey(accountKey, "runtime")
|
||||
machineID = normalizeMachineIDOrFallback(machineID, lookupKey)
|
||||
|
||||
m.mu.RLock()
|
||||
if fp, ok := m.fingerprints[lookupKey]; ok && fp.KiroHash == machineID {
|
||||
m.mu.RUnlock()
|
||||
return fp
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if fp, ok := m.fingerprints[lookupKey]; ok && fp.KiroHash == machineID {
|
||||
return fp
|
||||
}
|
||||
fp := generateRuntimeFingerprint(lookupKey, machineID)
|
||||
m.fingerprints[lookupKey] = fp
|
||||
return fp
|
||||
}
|
||||
|
||||
func generateRuntimeFingerprint(accountKey, machineID string) *RuntimeFingerprint {
|
||||
hash := sha256.Sum256([]byte(accountKey))
|
||||
seed := int64(binary.BigEndian.Uint64(hash[:8]))
|
||||
rng := rand.New(rand.NewSource(seed))
|
||||
|
||||
osType := goOSToNodePlatform(runtime.GOOS)
|
||||
if !containsString(osTypes, osType) {
|
||||
osType = osTypes[rng.Intn(len(osTypes))]
|
||||
}
|
||||
osVersionPool := osVersions[osType]
|
||||
if len(osVersionPool) == 0 {
|
||||
osVersionPool = osVersions["darwin"]
|
||||
}
|
||||
|
||||
return &RuntimeFingerprint{
|
||||
OIDCSDKVersion: oidcSDKVersions[rng.Intn(len(oidcSDKVersions))],
|
||||
RuntimeSDKVersion: runtimeSDKVersions[rng.Intn(len(runtimeSDKVersions))],
|
||||
StreamingSDKVersion: streamingSDKVersions[rng.Intn(len(streamingSDKVersions))],
|
||||
OSType: osType,
|
||||
OSVersion: osVersionPool[rng.Intn(len(osVersionPool))],
|
||||
NodeVersion: nodeVersions[rng.Intn(len(nodeVersions))],
|
||||
KiroVersion: kiroVersions[rng.Intn(len(kiroVersions))],
|
||||
KiroHash: machineID,
|
||||
}
|
||||
}
|
||||
|
||||
func goOSToNodePlatform(goos string) string {
|
||||
switch strings.TrimSpace(goos) {
|
||||
case "windows":
|
||||
return "win32"
|
||||
default:
|
||||
return strings.TrimSpace(goos)
|
||||
}
|
||||
}
|
||||
|
||||
func containsString(items []string, target string) bool {
|
||||
for _, item := range items {
|
||||
if item == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func BuildAccountKey(clientID, clientIDHash, refreshToken, profileArn string, accountID int64) string {
|
||||
switch {
|
||||
case strings.TrimSpace(clientIDHash) != "":
|
||||
return clientIDHash
|
||||
case strings.TrimSpace(clientID) != "":
|
||||
return shortSHA(clientID)
|
||||
case strings.TrimSpace(refreshToken) != "":
|
||||
return shortSHA(refreshToken)
|
||||
case strings.TrimSpace(profileArn) != "":
|
||||
return shortSHA(profileArn)
|
||||
case accountID > 0:
|
||||
return shortSHA(fmt.Sprintf("account:%d", accountID))
|
||||
default:
|
||||
return shortSHA(uuid.NewString())
|
||||
}
|
||||
}
|
||||
|
||||
func NormalizeMachineID(machineID string) (string, bool) {
|
||||
trimmed := strings.TrimSpace(machineID)
|
||||
if len(trimmed) == 64 && isHexString(trimmed) {
|
||||
return strings.ToLower(trimmed), true
|
||||
}
|
||||
withoutDashes := strings.ReplaceAll(trimmed, "-", "")
|
||||
if len(withoutDashes) == 32 && isHexString(withoutDashes) {
|
||||
normalized := strings.ToLower(withoutDashes)
|
||||
return normalized + normalized, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func BuildMachineID(refreshToken, apiKey, fallbackKey string) string {
|
||||
if refreshToken = strings.TrimSpace(refreshToken); refreshToken != "" {
|
||||
return sha256Hex("KotlinNativeAPI/" + refreshToken)
|
||||
}
|
||||
if apiKey = strings.TrimSpace(apiKey); apiKey != "" {
|
||||
return sha256Hex("KiroAPIKey/" + apiKey)
|
||||
}
|
||||
if fallbackKey = strings.TrimSpace(fallbackKey); fallbackKey != "" {
|
||||
return sha256Hex("KiroFallback/" + fallbackKey)
|
||||
}
|
||||
return sha256Hex("KiroFallback/default")
|
||||
}
|
||||
|
||||
func shortSHA(seed string) string {
|
||||
sum := sha256.Sum256([]byte(seed))
|
||||
return hex.EncodeToString(sum[:8])
|
||||
}
|
||||
|
||||
func sha256Hex(seed string) string {
|
||||
sum := sha256.Sum256([]byte(seed))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func isHexString(value string) bool {
|
||||
for _, c := range value {
|
||||
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func normalizeMachineIDOrFallback(machineID, fallbackKey string) string {
|
||||
if normalized, ok := NormalizeMachineID(machineID); ok {
|
||||
return normalized
|
||||
}
|
||||
return BuildMachineID("", "", fallbackKey)
|
||||
}
|
||||
|
||||
func fingerprintLookupKey(accountKey, fallback string) string {
|
||||
key := strings.TrimSpace(accountKey)
|
||||
if key != "" {
|
||||
return key
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func BuildRuntimeUserAgent(accountKey, machineID string) string {
|
||||
fp := globalRuntimeFingerprints().Get(accountKey, machineID)
|
||||
return fmt.Sprintf(
|
||||
"aws-sdk-js/%s ua/2.1 os/%s#%s lang/js md/nodejs#%s api/codewhispererstreaming#%s m/E KiroIDE-%s-%s",
|
||||
fp.StreamingSDKVersion,
|
||||
fp.OSType,
|
||||
fp.OSVersion,
|
||||
fp.NodeVersion,
|
||||
fp.StreamingSDKVersion,
|
||||
fp.KiroVersion,
|
||||
fp.KiroHash,
|
||||
)
|
||||
}
|
||||
|
||||
func BuildRuntimeAmzUserAgent(accountKey, machineID string) string {
|
||||
fp := globalRuntimeFingerprints().Get(accountKey, machineID)
|
||||
return fmt.Sprintf(
|
||||
"aws-sdk-js/%s KiroIDE-%s-%s",
|
||||
fp.StreamingSDKVersion,
|
||||
fp.KiroVersion,
|
||||
fp.KiroHash,
|
||||
)
|
||||
}
|
||||
|
||||
func BuildOIDCHeaders(accountKey, machineID string) map[string]string {
|
||||
fp := globalRuntimeFingerprints().Get(fingerprintLookupKey(accountKey, "oidc-session"), machineID)
|
||||
return map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
"x-amz-user-agent": fmt.Sprintf("aws-sdk-js/%s KiroIDE", fp.OIDCSDKVersion),
|
||||
"User-Agent": fmt.Sprintf("aws-sdk-js/%s ua/2.1 os/%s#%s lang/js md/nodejs#%s api/sso-oidc#%s m/E KiroIDE", fp.OIDCSDKVersion, fp.OSType, fp.OSVersion, fp.NodeVersion, fp.OIDCSDKVersion),
|
||||
"amz-sdk-invocation-id": uuid.NewString(),
|
||||
"amz-sdk-request": "attempt=1; max=4",
|
||||
}
|
||||
}
|
||||
|
||||
func BuildLoginHeaders(accountKey, machineID string) map[string]string {
|
||||
fp := globalRuntimeFingerprints().Get(fingerprintLookupKey(accountKey, "login"), machineID)
|
||||
return map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": fmt.Sprintf("KiroIDE-%s-%s", fp.KiroVersion, fp.KiroHash),
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
}
|
||||
}
|
||||
|
||||
func ExponentialBackoffWithJitter(attempt int, baseDelay, maxDelay time.Duration) time.Duration {
|
||||
if attempt < 0 {
|
||||
attempt = 0
|
||||
}
|
||||
delay := baseDelay << attempt
|
||||
if delay > maxDelay {
|
||||
delay = maxDelay
|
||||
}
|
||||
const jitterFactor = 0.3
|
||||
seed := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
jitter := 1 + ((seed.Float64()*2 - 1) * jitterFactor)
|
||||
return time.Duration(float64(delay) * jitter)
|
||||
}
|
||||
Reference in New Issue
Block a user