forked from baron/baron-sso
linked RP 응답에 1st-party 앱 자동 로그인 init_url 추가
This commit is contained in:
@@ -4483,7 +4483,8 @@ type linkedRpSummary struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Logo string `json:"logo,omitempty"`
|
||||
URL string `json:"url,omitempty"` // Added
|
||||
URL string `json:"url,omitempty"`
|
||||
InitURL string `json:"init_url,omitempty"`
|
||||
LastAuthenticatedAt string `json:"lastAuthenticatedAt,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
@@ -4564,17 +4565,19 @@ func (h *AuthHandler) ListLinkedRps(c *fiber.Ctx) error {
|
||||
if len(scopes) == 0 && strings.TrimSpace(client.Scope) != "" {
|
||||
scopes = strings.Fields(client.Scope)
|
||||
}
|
||||
initURL := resolveLinkedRPInitURL(client.ClientID, scopes, client.RedirectURIs)
|
||||
|
||||
existing := records[clientID]
|
||||
if existing == nil {
|
||||
records[clientID] = &linkedRpRecord{
|
||||
linkedRpSummary: linkedRpSummary{
|
||||
ID: clientID,
|
||||
Name: name,
|
||||
Logo: extractHydraClientLogo(client.Metadata),
|
||||
URL: clientURL,
|
||||
Status: "active", // Hydra 세션이 있으면 활성
|
||||
Scopes: scopes,
|
||||
ID: clientID,
|
||||
Name: name,
|
||||
Logo: extractHydraClientLogo(client.Metadata),
|
||||
URL: clientURL,
|
||||
InitURL: initURL,
|
||||
Status: "active", // Hydra 세션이 있으면 활성
|
||||
Scopes: scopes,
|
||||
},
|
||||
lastAuth: lastAuth,
|
||||
}
|
||||
@@ -4590,6 +4593,9 @@ func (h *AuthHandler) ListLinkedRps(c *fiber.Ctx) error {
|
||||
if existing.URL == "" {
|
||||
existing.URL = clientURL
|
||||
}
|
||||
if existing.InitURL == "" {
|
||||
existing.InitURL = initURL
|
||||
}
|
||||
existing.Scopes = mergeScopes(existing.Scopes, scopes)
|
||||
if lastAuth.After(existing.lastAuth) {
|
||||
existing.lastAuth = lastAuth
|
||||
@@ -4644,15 +4650,21 @@ func (h *AuthHandler) ListLinkedRps(c *fiber.Ctx) error {
|
||||
client.ClientURI,
|
||||
client.RedirectURIs,
|
||||
)
|
||||
initURL := resolveLinkedRPInitURL(
|
||||
client.ClientID,
|
||||
dc.GrantedScopes,
|
||||
client.RedirectURIs,
|
||||
)
|
||||
|
||||
records[dc.ClientID] = &linkedRpRecord{
|
||||
linkedRpSummary: linkedRpSummary{
|
||||
ID: dc.ClientID,
|
||||
Name: name,
|
||||
Logo: extractHydraClientLogo(client.Metadata),
|
||||
URL: clientURL,
|
||||
Status: status,
|
||||
Scopes: dc.GrantedScopes,
|
||||
ID: dc.ClientID,
|
||||
Name: name,
|
||||
Logo: extractHydraClientLogo(client.Metadata),
|
||||
URL: clientURL,
|
||||
InitURL: initURL,
|
||||
Status: status,
|
||||
Scopes: dc.GrantedScopes,
|
||||
},
|
||||
lastAuth: dc.UpdatedAt,
|
||||
}
|
||||
@@ -4726,6 +4738,11 @@ func (h *AuthHandler) ListLinkedRps(c *fiber.Ctx) error {
|
||||
}
|
||||
}
|
||||
record.URL = clientURL
|
||||
record.InitURL = resolveLinkedRPInitURL(
|
||||
client.ClientID,
|
||||
scopes,
|
||||
client.RedirectURIs,
|
||||
)
|
||||
} else {
|
||||
// Hydra 정보 없음 (삭제됨 등) -> Audit 정보나 ID로 대체
|
||||
if record.Name == "" {
|
||||
@@ -6778,6 +6795,63 @@ func resolveLinkedRPURL(clientID string, clientURI string, redirectURIs []string
|
||||
return ""
|
||||
}
|
||||
|
||||
func resolveLinkedRPInitURL(clientID string, scopes []string, redirectURIs []string) string {
|
||||
clientID = strings.TrimSpace(clientID)
|
||||
if clientID == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch clientID {
|
||||
case "adminfront":
|
||||
if value := strings.TrimRight(strings.TrimSpace(os.Getenv("ADMINFRONT_URL")), "/"); value != "" {
|
||||
return value + "/login?auto=1"
|
||||
}
|
||||
case "devfront":
|
||||
if value := strings.TrimRight(strings.TrimSpace(os.Getenv("DEVFRONT_URL")), "/"); value != "" {
|
||||
return value + "/login?auto=1&returnTo=%2Fclients"
|
||||
}
|
||||
}
|
||||
|
||||
hydraPublicURL := strings.TrimRight(os.Getenv("HYDRA_PUBLIC_URL"), "/")
|
||||
if hydraPublicURL == "" {
|
||||
userfrontURL := strings.TrimRight(os.Getenv("USERFRONT_URL"), "/")
|
||||
if userfrontURL == "" {
|
||||
userfrontURL = "https://sso.hmac.kr"
|
||||
}
|
||||
hydraPublicURL = userfrontURL + "/oidc"
|
||||
}
|
||||
|
||||
redirectURI := ""
|
||||
if len(redirectURIs) > 0 {
|
||||
redirectURI = strings.TrimSpace(redirectURIs[0])
|
||||
}
|
||||
|
||||
mergedScopes := make([]string, 0, len(scopes)+1)
|
||||
seen := map[string]struct{}{}
|
||||
for _, scope := range append([]string{"openid"}, scopes...) {
|
||||
scope = strings.TrimSpace(scope)
|
||||
if scope == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[scope]; ok {
|
||||
continue
|
||||
}
|
||||
seen[scope] = struct{}{}
|
||||
mergedScopes = append(mergedScopes, scope)
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Set("client_id", clientID)
|
||||
params.Set("response_type", "code")
|
||||
params.Set("scope", strings.Join(mergedScopes, " "))
|
||||
params.Set("state", GenerateSecureAlnumToken(16))
|
||||
if redirectURI != "" {
|
||||
params.Set("redirect_uri", redirectURI)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/oauth2/auth?%s", hydraPublicURL, params.Encode())
|
||||
}
|
||||
|
||||
func mergeScopes(current []string, next []string) []string {
|
||||
if len(next) == 0 {
|
||||
return current
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -45,11 +46,14 @@ func TestListLinkedRps_PriorityAndAggregation(t *testing.T) {
|
||||
return httpJSONAny(r, http.StatusOK, []map[string]interface{}{
|
||||
{
|
||||
"client": map[string]interface{}{
|
||||
"client_id": "client-active",
|
||||
"client_name": "Active App",
|
||||
"client_id": "devfront",
|
||||
"client_name": "DevFront",
|
||||
"redirect_uris": []string{
|
||||
"https://active.example.com/callback",
|
||||
},
|
||||
},
|
||||
"granted_scope": []string{"openid"},
|
||||
"handled_at": time.Now().Format(time.RFC3339),
|
||||
"grant_scope": []string{"openid", "profile"},
|
||||
"handled_at": time.Now().Format(time.RFC3339),
|
||||
},
|
||||
}), nil
|
||||
}
|
||||
@@ -111,6 +115,8 @@ func TestListLinkedRps_PriorityAndAggregation(t *testing.T) {
|
||||
|
||||
t.Setenv("KRATOS_PUBLIC_URL", "http://kratos.test")
|
||||
t.Setenv("KRATOS_ADMIN_URL", "http://kratos.test")
|
||||
t.Setenv("HYDRA_PUBLIC_URL", "https://sso.example.com/oidc")
|
||||
t.Setenv("DEVFRONT_URL", "http://localhost:5174")
|
||||
|
||||
app := newLinkedRpTestApp(h)
|
||||
|
||||
@@ -123,10 +129,11 @@ func TestListLinkedRps_PriorityAndAggregation(t *testing.T) {
|
||||
|
||||
var res struct {
|
||||
Items []struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Scopes []string `json:"scopes"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Scopes []string `json:"scopes"`
|
||||
InitURL string `json:"init_url"`
|
||||
} `json:"items"`
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&res)
|
||||
@@ -138,7 +145,23 @@ func TestListLinkedRps_PriorityAndAggregation(t *testing.T) {
|
||||
statusMap[item.ID] = item.Status
|
||||
}
|
||||
|
||||
assert.Equal(t, "active", statusMap["client-active"])
|
||||
assert.Equal(t, "active", statusMap["devfront"])
|
||||
assert.Equal(t, "inactive", statusMap["client-consent"])
|
||||
assert.Equal(t, "inactive", statusMap["client-audit"])
|
||||
|
||||
var activeInitURL string
|
||||
for _, item := range res.Items {
|
||||
if item.ID == "devfront" {
|
||||
activeInitURL = item.InitURL
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
parsedInitURL, err := url.Parse(activeInitURL)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "http", parsedInitURL.Scheme)
|
||||
assert.Equal(t, "localhost:5174", parsedInitURL.Host)
|
||||
assert.Equal(t, "/login", parsedInitURL.Path)
|
||||
assert.Equal(t, "1", parsedInitURL.Query().Get("auto"))
|
||||
assert.Equal(t, "/clients", parsedInitURL.Query().Get("returnTo"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user