mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-03-07 22:33:30 +00:00
Add ExtractIDCIdentifier and GenerateTokenFileName functions to centralize token filename generation logic. This improves code maintainability by: - Extracting IDC identifier from startUrl for unique token file naming - Supporting priority-based filename generation (email > startUrl > authMethod) - Removing duplicate filename generation code from oauth_web.go - Adding comprehensive unit tests for the new functions
312 lines
7.3 KiB
Go
312 lines
7.3 KiB
Go
package kiro
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"testing"
|
|
)
|
|
|
|
func TestExtractEmailFromJWT(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
token string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "Empty token",
|
|
token: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "Invalid token format",
|
|
token: "not.a.valid.jwt",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "Invalid token - not base64",
|
|
token: "xxx.yyy.zzz",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "Valid JWT with email",
|
|
token: createTestJWT(map[string]any{"email": "test@example.com", "sub": "user123"}),
|
|
expected: "test@example.com",
|
|
},
|
|
{
|
|
name: "JWT without email but with preferred_username",
|
|
token: createTestJWT(map[string]any{"preferred_username": "user@domain.com", "sub": "user123"}),
|
|
expected: "user@domain.com",
|
|
},
|
|
{
|
|
name: "JWT with email-like sub",
|
|
token: createTestJWT(map[string]any{"sub": "another@test.com"}),
|
|
expected: "another@test.com",
|
|
},
|
|
{
|
|
name: "JWT without any email fields",
|
|
token: createTestJWT(map[string]any{"sub": "user123", "name": "Test User"}),
|
|
expected: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := ExtractEmailFromJWT(tt.token)
|
|
if result != tt.expected {
|
|
t.Errorf("ExtractEmailFromJWT() = %q, want %q", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSanitizeEmailForFilename(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
email string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "Empty email",
|
|
email: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "Simple email",
|
|
email: "user@example.com",
|
|
expected: "user@example.com",
|
|
},
|
|
{
|
|
name: "Email with space",
|
|
email: "user name@example.com",
|
|
expected: "user_name@example.com",
|
|
},
|
|
{
|
|
name: "Email with special chars",
|
|
email: "user:name@example.com",
|
|
expected: "user_name@example.com",
|
|
},
|
|
{
|
|
name: "Email with multiple special chars",
|
|
email: "user/name:test@example.com",
|
|
expected: "user_name_test@example.com",
|
|
},
|
|
{
|
|
name: "Path traversal attempt",
|
|
email: "../../../etc/passwd",
|
|
expected: "_.__.__._etc_passwd",
|
|
},
|
|
{
|
|
name: "Path traversal with backslash",
|
|
email: `..\..\..\..\windows\system32`,
|
|
expected: "_.__.__.__._windows_system32",
|
|
},
|
|
{
|
|
name: "Null byte injection attempt",
|
|
email: "user\x00@evil.com",
|
|
expected: "user_@evil.com",
|
|
},
|
|
// URL-encoded path traversal tests
|
|
{
|
|
name: "URL-encoded slash",
|
|
email: "user%2Fpath@example.com",
|
|
expected: "user_path@example.com",
|
|
},
|
|
{
|
|
name: "URL-encoded backslash",
|
|
email: "user%5Cpath@example.com",
|
|
expected: "user_path@example.com",
|
|
},
|
|
{
|
|
name: "URL-encoded dot",
|
|
email: "%2E%2E%2Fetc%2Fpasswd",
|
|
expected: "___etc_passwd",
|
|
},
|
|
{
|
|
name: "URL-encoded null",
|
|
email: "user%00@evil.com",
|
|
expected: "user_@evil.com",
|
|
},
|
|
{
|
|
name: "Double URL-encoding attack",
|
|
email: "%252F%252E%252E",
|
|
expected: "_252F_252E_252E", // % replaced with _, remaining chars preserved (safe)
|
|
},
|
|
{
|
|
name: "Mixed case URL-encoding",
|
|
email: "%2f%2F%5c%5C",
|
|
expected: "____",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := SanitizeEmailForFilename(tt.email)
|
|
if result != tt.expected {
|
|
t.Errorf("SanitizeEmailForFilename() = %q, want %q", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// createTestJWT creates a test JWT token with the given claims
|
|
func createTestJWT(claims map[string]any) string {
|
|
header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"RS256","typ":"JWT"}`))
|
|
|
|
payloadBytes, _ := json.Marshal(claims)
|
|
payload := base64.RawURLEncoding.EncodeToString(payloadBytes)
|
|
|
|
signature := base64.RawURLEncoding.EncodeToString([]byte("fake-signature"))
|
|
|
|
return header + "." + payload + "." + signature
|
|
}
|
|
|
|
func TestExtractIDCIdentifier(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
startURL string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "Empty URL",
|
|
startURL: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "Standard IDC URL with d- prefix",
|
|
startURL: "https://d-1234567890.awsapps.com/start",
|
|
expected: "d-1234567890",
|
|
},
|
|
{
|
|
name: "IDC URL with company name",
|
|
startURL: "https://my-company.awsapps.com/start",
|
|
expected: "my-company",
|
|
},
|
|
{
|
|
name: "IDC URL with simple name",
|
|
startURL: "https://acme-corp.awsapps.com/start",
|
|
expected: "acme-corp",
|
|
},
|
|
{
|
|
name: "IDC URL without https",
|
|
startURL: "http://d-9876543210.awsapps.com/start",
|
|
expected: "d-9876543210",
|
|
},
|
|
{
|
|
name: "IDC URL with subdomain only",
|
|
startURL: "https://test.awsapps.com/start",
|
|
expected: "test",
|
|
},
|
|
{
|
|
name: "Builder ID URL",
|
|
startURL: "https://view.awsapps.com/start",
|
|
expected: "view",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := ExtractIDCIdentifier(tt.startURL)
|
|
if result != tt.expected {
|
|
t.Errorf("ExtractIDCIdentifier() = %q, want %q", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerateTokenFileName(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
tokenData *KiroTokenData
|
|
expected string
|
|
}{
|
|
{
|
|
name: "IDC with email",
|
|
tokenData: &KiroTokenData{
|
|
AuthMethod: "idc",
|
|
Email: "user@example.com",
|
|
StartURL: "https://d-1234567890.awsapps.com/start",
|
|
},
|
|
expected: "kiro-idc-user-example-com.json",
|
|
},
|
|
{
|
|
name: "IDC without email but with startUrl",
|
|
tokenData: &KiroTokenData{
|
|
AuthMethod: "idc",
|
|
Email: "",
|
|
StartURL: "https://d-1234567890.awsapps.com/start",
|
|
},
|
|
expected: "kiro-idc-d-1234567890.json",
|
|
},
|
|
{
|
|
name: "IDC with company name in startUrl",
|
|
tokenData: &KiroTokenData{
|
|
AuthMethod: "idc",
|
|
Email: "",
|
|
StartURL: "https://my-company.awsapps.com/start",
|
|
},
|
|
expected: "kiro-idc-my-company.json",
|
|
},
|
|
{
|
|
name: "IDC without email and without startUrl",
|
|
tokenData: &KiroTokenData{
|
|
AuthMethod: "idc",
|
|
Email: "",
|
|
StartURL: "",
|
|
},
|
|
expected: "kiro-idc.json",
|
|
},
|
|
{
|
|
name: "Builder ID with email",
|
|
tokenData: &KiroTokenData{
|
|
AuthMethod: "builder-id",
|
|
Email: "user@gmail.com",
|
|
StartURL: "https://view.awsapps.com/start",
|
|
},
|
|
expected: "kiro-builder-id-user-gmail-com.json",
|
|
},
|
|
{
|
|
name: "Builder ID without email",
|
|
tokenData: &KiroTokenData{
|
|
AuthMethod: "builder-id",
|
|
Email: "",
|
|
StartURL: "https://view.awsapps.com/start",
|
|
},
|
|
expected: "kiro-builder-id.json",
|
|
},
|
|
{
|
|
name: "Social auth with email",
|
|
tokenData: &KiroTokenData{
|
|
AuthMethod: "google",
|
|
Email: "user@gmail.com",
|
|
},
|
|
expected: "kiro-google-user-gmail-com.json",
|
|
},
|
|
{
|
|
name: "Empty auth method",
|
|
tokenData: &KiroTokenData{
|
|
AuthMethod: "",
|
|
Email: "",
|
|
},
|
|
expected: "kiro-unknown.json",
|
|
},
|
|
{
|
|
name: "Email with special characters",
|
|
tokenData: &KiroTokenData{
|
|
AuthMethod: "idc",
|
|
Email: "user.name+tag@sub.example.com",
|
|
StartURL: "https://d-1234567890.awsapps.com/start",
|
|
},
|
|
expected: "kiro-idc-user-name+tag-sub-example-com.json",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := GenerateTokenFileName(tt.tokenData)
|
|
if result != tt.expected {
|
|
t.Errorf("GenerateTokenFileName() = %q, want %q", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|