forked from baron/baron-sso
OIDC back-channel logout 백엔드 전송 기능 추가
This commit is contained in:
@@ -94,19 +94,21 @@ type devStatsResponse struct {
|
||||
}
|
||||
|
||||
type clientSummary struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt *time.Time `json:"createdAt,omitempty"`
|
||||
RedirectURIs []string `json:"redirectUris"`
|
||||
Scopes []string `json:"scopes"`
|
||||
ClientSecret string `json:"clientSecret,omitempty"`
|
||||
TokenEndpointAuthMethod string `json:"tokenEndpointAuthMethod,omitempty"`
|
||||
SkipConsent bool `json:"skipConsent"`
|
||||
JwksUri string `json:"jwksUri,omitempty"`
|
||||
Jwks interface{} `json:"jwks,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt *time.Time `json:"createdAt,omitempty"`
|
||||
RedirectURIs []string `json:"redirectUris"`
|
||||
Scopes []string `json:"scopes"`
|
||||
ClientSecret string `json:"clientSecret,omitempty"`
|
||||
TokenEndpointAuthMethod string `json:"tokenEndpointAuthMethod,omitempty"`
|
||||
SkipConsent bool `json:"skipConsent"`
|
||||
JwksUri string `json:"jwksUri,omitempty"`
|
||||
Jwks interface{} `json:"jwks,omitempty"`
|
||||
BackchannelLogoutURI string `json:"backchannelLogoutUri,omitempty"`
|
||||
BackchannelLogoutSessionRequired bool `json:"backchannelLogoutSessionRequired"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type clientListResponse struct {
|
||||
@@ -179,19 +181,21 @@ type consentListResponse struct {
|
||||
}
|
||||
|
||||
type clientUpsertRequest struct {
|
||||
ID *string `json:"id"`
|
||||
Name *string `json:"name"`
|
||||
Type *string `json:"type"`
|
||||
Status *string `json:"status"`
|
||||
RedirectURIs *[]string `json:"redirectUris"`
|
||||
Scopes *[]string `json:"scopes"`
|
||||
GrantTypes *[]string `json:"grantTypes"`
|
||||
ResponseTypes *[]string `json:"responseTypes"`
|
||||
TokenEndpointAuthMethod *string `json:"tokenEndpointAuthMethod"`
|
||||
SkipConsent *bool `json:"skipConsent"`
|
||||
JwksUri *string `json:"jwksUri"`
|
||||
Jwks interface{} `json:"jwks"`
|
||||
Metadata *map[string]interface{} `json:"metadata"`
|
||||
ID *string `json:"id"`
|
||||
Name *string `json:"name"`
|
||||
Type *string `json:"type"`
|
||||
Status *string `json:"status"`
|
||||
RedirectURIs *[]string `json:"redirectUris"`
|
||||
Scopes *[]string `json:"scopes"`
|
||||
GrantTypes *[]string `json:"grantTypes"`
|
||||
ResponseTypes *[]string `json:"responseTypes"`
|
||||
TokenEndpointAuthMethod *string `json:"tokenEndpointAuthMethod"`
|
||||
SkipConsent *bool `json:"skipConsent"`
|
||||
JwksUri *string `json:"jwksUri"`
|
||||
Jwks interface{} `json:"jwks"`
|
||||
BackchannelLogoutURI *string `json:"backchannelLogoutUri"`
|
||||
BackchannelLogoutSessionRequired *bool `json:"backchannelLogoutSessionRequired"`
|
||||
Metadata *map[string]interface{} `json:"metadata"`
|
||||
}
|
||||
|
||||
type normalizedIDTokenClaim struct {
|
||||
@@ -1679,9 +1683,15 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
|
||||
if tenantID != "" {
|
||||
metadata["tenant_id"] = tenantID
|
||||
}
|
||||
var err error
|
||||
metadata["status"] = status
|
||||
metadata["created_at"] = time.Now().Format(time.RFC3339)
|
||||
var err error
|
||||
backchannelLogoutURI := strings.TrimSpace(valueOr(req.BackchannelLogoutURI, ""))
|
||||
backchannelLogoutSessionRequired := valueOrBool(req.BackchannelLogoutSessionRequired, false)
|
||||
metadata, err = normalizeBackchannelLogoutMetadata(metadata, backchannelLogoutURI, backchannelLogoutSessionRequired)
|
||||
if err != nil {
|
||||
return errorJSON(c, fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
metadata, err = normalizeClientTenantAccessMetadata(metadata)
|
||||
if err != nil {
|
||||
return errorJSON(c, fiber.StatusBadRequest, err.Error())
|
||||
@@ -1711,17 +1721,19 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
|
||||
)
|
||||
|
||||
clientReq := domain.HydraClient{
|
||||
ClientID: clientID,
|
||||
ClientName: name,
|
||||
RedirectURIs: redirectURIs,
|
||||
GrantTypes: grantTypes,
|
||||
ResponseTypes: responseTypes,
|
||||
Scope: strings.Join(scopes, " "),
|
||||
TokenEndpointAuthMethod: tokenAuthMethod,
|
||||
SkipConsent: boolPtr(valueOrBool(req.SkipConsent, true)),
|
||||
JWKSUri: jwksURI,
|
||||
JWKS: jwks,
|
||||
Metadata: metadata,
|
||||
ClientID: clientID,
|
||||
ClientName: name,
|
||||
RedirectURIs: redirectURIs,
|
||||
GrantTypes: grantTypes,
|
||||
ResponseTypes: responseTypes,
|
||||
Scope: strings.Join(scopes, " "),
|
||||
TokenEndpointAuthMethod: tokenAuthMethod,
|
||||
SkipConsent: boolPtr(valueOrBool(req.SkipConsent, true)),
|
||||
JWKSUri: jwksURI,
|
||||
JWKS: jwks,
|
||||
BackChannelLogoutURI: backchannelLogoutURI,
|
||||
BackChannelLogoutSessionRequired: boolPtr(backchannelLogoutSessionRequired),
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
h.setAuditDetailsExtra(c, map[string]any{
|
||||
@@ -1866,6 +1878,16 @@ func (h *DevHandler) UpdateClient(c *fiber.Ctx) error {
|
||||
}
|
||||
metadata["status"] = status
|
||||
}
|
||||
resolvedBackchannelLogoutURI := valueOr(req.BackchannelLogoutURI, current.BackchannelLogoutURI())
|
||||
resolvedBackchannelLogoutSessionRequired := valueOrBool(req.BackchannelLogoutSessionRequired, current.BackchannelLogoutSessionRequiredValue())
|
||||
metadata, err = normalizeBackchannelLogoutMetadata(
|
||||
metadata,
|
||||
resolvedBackchannelLogoutURI,
|
||||
resolvedBackchannelLogoutSessionRequired,
|
||||
)
|
||||
if err != nil {
|
||||
return errorJSON(c, fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
metadata, err = normalizeClientTenantAccessMetadata(metadata)
|
||||
if err != nil {
|
||||
return errorJSON(c, fiber.StatusBadRequest, err.Error())
|
||||
@@ -1901,17 +1923,19 @@ func (h *DevHandler) UpdateClient(c *fiber.Ctx) error {
|
||||
resolvedSkipConsent := valueOrBool(req.SkipConsent, valueOrBool(current.SkipConsent, true))
|
||||
|
||||
updated := domain.HydraClient{
|
||||
ClientID: current.ClientID,
|
||||
ClientName: valueOr(req.Name, current.ClientName),
|
||||
RedirectURIs: derefSlice(req.RedirectURIs, current.RedirectURIs),
|
||||
GrantTypes: derefSlice(req.GrantTypes, current.GrantTypes),
|
||||
ResponseTypes: derefSlice(req.ResponseTypes, current.ResponseTypes),
|
||||
Scope: buildScope(valueOrSlice(req.Scopes, strings.Fields(current.Scope))),
|
||||
TokenEndpointAuthMethod: resolvedTokenAuthMethod,
|
||||
SkipConsent: boolPtr(resolvedSkipConsent),
|
||||
JWKSUri: resolvedJWKSURI,
|
||||
JWKS: resolvedJWKS,
|
||||
Metadata: metadata,
|
||||
ClientID: current.ClientID,
|
||||
ClientName: valueOr(req.Name, current.ClientName),
|
||||
RedirectURIs: derefSlice(req.RedirectURIs, current.RedirectURIs),
|
||||
GrantTypes: derefSlice(req.GrantTypes, current.GrantTypes),
|
||||
ResponseTypes: derefSlice(req.ResponseTypes, current.ResponseTypes),
|
||||
Scope: buildScope(valueOrSlice(req.Scopes, strings.Fields(current.Scope))),
|
||||
TokenEndpointAuthMethod: resolvedTokenAuthMethod,
|
||||
SkipConsent: boolPtr(resolvedSkipConsent),
|
||||
JWKSUri: resolvedJWKSURI,
|
||||
JWKS: resolvedJWKS,
|
||||
BackChannelLogoutURI: strings.TrimSpace(resolvedBackchannelLogoutURI),
|
||||
BackChannelLogoutSessionRequired: boolPtr(resolvedBackchannelLogoutSessionRequired),
|
||||
Metadata: metadata,
|
||||
}
|
||||
if err := validateReservedSystemClientName(updated.ClientID, updated.ClientName); err != nil {
|
||||
return errorJSON(c, fiber.StatusForbidden, err.Error())
|
||||
@@ -2651,19 +2675,21 @@ func (h *DevHandler) mapClientSummary(client domain.HydraClient) clientSummary {
|
||||
}
|
||||
|
||||
return clientSummary{
|
||||
ID: client.ClientID,
|
||||
Name: name,
|
||||
Type: clientType,
|
||||
Status: status,
|
||||
CreatedAt: createdAt,
|
||||
RedirectURIs: client.RedirectURIs,
|
||||
Scopes: scopes,
|
||||
ClientSecret: clientSecret,
|
||||
TokenEndpointAuthMethod: client.TokenEndpointAuthMethod,
|
||||
SkipConsent: valueOrBool(client.SkipConsent, true),
|
||||
JwksUri: client.JWKSUri,
|
||||
Jwks: client.JWKS,
|
||||
Metadata: client.Metadata,
|
||||
ID: client.ClientID,
|
||||
Name: name,
|
||||
Type: clientType,
|
||||
Status: status,
|
||||
CreatedAt: createdAt,
|
||||
RedirectURIs: client.RedirectURIs,
|
||||
Scopes: scopes,
|
||||
ClientSecret: clientSecret,
|
||||
TokenEndpointAuthMethod: client.TokenEndpointAuthMethod,
|
||||
SkipConsent: valueOrBool(client.SkipConsent, true),
|
||||
JwksUri: client.JWKSUri,
|
||||
Jwks: client.JWKS,
|
||||
BackchannelLogoutURI: client.BackchannelLogoutURI(),
|
||||
BackchannelLogoutSessionRequired: client.BackchannelLogoutSessionRequiredValue(),
|
||||
Metadata: client.Metadata,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2683,6 +2709,58 @@ func readMetadataBoolValue(metadata map[string]interface{}, key string) bool {
|
||||
return value
|
||||
}
|
||||
|
||||
func normalizeBackchannelLogoutMetadata(metadata map[string]interface{}, logoutURI string, sessionRequired bool) (map[string]interface{}, error) {
|
||||
if metadata == nil {
|
||||
metadata = map[string]interface{}{}
|
||||
}
|
||||
|
||||
trimmedURI := strings.TrimSpace(logoutURI)
|
||||
if err := validateBackchannelLogoutURI(trimmedURI); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if trimmedURI == "" {
|
||||
delete(metadata, domain.MetadataBackChannelLogoutURI)
|
||||
delete(metadata, domain.MetadataBackChannelLogoutSessionRequired)
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
metadata[domain.MetadataBackChannelLogoutURI] = trimmedURI
|
||||
metadata[domain.MetadataBackChannelLogoutSessionRequired] = sessionRequired
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
func validateBackchannelLogoutURI(raw string) error {
|
||||
trimmed := strings.TrimSpace(raw)
|
||||
if trimmed == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
parsed, err := url.Parse(trimmed)
|
||||
if err != nil || parsed == nil {
|
||||
return fmt.Errorf("backchannelLogoutUri must be a valid absolute URL")
|
||||
}
|
||||
if parsed.Scheme == "" || parsed.Host == "" {
|
||||
return fmt.Errorf("backchannelLogoutUri must be a valid absolute URL")
|
||||
}
|
||||
if parsed.Fragment != "" {
|
||||
return fmt.Errorf("backchannelLogoutUri must not include a fragment")
|
||||
}
|
||||
|
||||
switch strings.ToLower(parsed.Scheme) {
|
||||
case "https":
|
||||
return nil
|
||||
case "http":
|
||||
host := strings.ToLower(parsed.Hostname())
|
||||
if host == "localhost" || host == "127.0.0.1" {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("backchannelLogoutUri must use https outside localhost development")
|
||||
default:
|
||||
return fmt.Errorf("backchannelLogoutUri must use http or https")
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeClientAutoLoginMetadata(metadata map[string]interface{}) (map[string]interface{}, error) {
|
||||
if metadata == nil {
|
||||
return metadata, nil
|
||||
|
||||
Reference in New Issue
Block a user