{"record":{"id":"aecc6af639ce4400","repo":"siyuan-note/siyuan","slug":"validate-oauth-issuer-w","errorCode":null,"errorMessage":"validate OAuth issuer: %w","messagePattern":"validate OAuth issuer: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/mcp/client/oauth.go","lineNumber":530,"sourceCode":"}\n\nfunc (h *mcpOAuthHandler) validateCredentialIssuer(ctx context.Context, credential oauthCredential) (bool, error) {\n\tvar challenges []oauthex.Challenge\n\tresource := h.server.URL\n\tif credential.ResourceMetadataURL != \"\" {\n\t\tchallenges = []oauthex.Challenge{{Scheme: \"bearer\", Params: map[string]string{\"resource_metadata\": credential.ResourceMetadataURL}}}\n\t\tresource = credential.Resource\n\t}\n\tprm, err := discoverProtectedResource(ctx, challenges, resource, h.client)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"validate OAuth protected resource: %w\", err)\n\t}\n\tif prm.Resource != credential.Resource || len(prm.AuthorizationServers) == 0 {\n\t\treturn false, nil\n\t}\n\tasm, err := auth.GetAuthServerMetadata(ctx, prm.AuthorizationServers[0], h.client)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"validate OAuth issuer: %w\", err)\n\t}\n\treturn asm != nil && asm.Issuer == credential.Issuer && asm.TokenEndpoint == credential.TokenEndpoint, nil\n}\n\nfunc protectedResourceURLs(metadataURL, resource string) []protectedResourceURL {\n\tvar result []protectedResourceURL\n\tif metadataURL != \"\" {\n\t\tresult = append(result, protectedResourceURL{URL: metadataURL, Resource: resource})\n\t}\n\tresourceURL, err := url.Parse(resource)\n\tif err != nil {\n\t\treturn result\n\t}\n\tmetadata := *resourceURL\n\tmetadata.RawPath = \"\"\n\tmetadata.RawQuery = \"\"\n\tmetadata.Fragment = \"\"\n\tmetadata.Path = \"/.well-known/oauth-protected-resource/\" + strings.TrimLeft(resourceURL.Path, \"/\")","sourceCodeStart":512,"sourceCodeEnd":548,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/mcp/client/oauth.go#L512-L548","documentation":"Wrapped error at oauth.go:528-530 from auth.GetAuthServerMetadata during validateCredentialIssuer. After PRM discovery confirmed the resource and authorization_servers[0], fetching the AS metadata document itself failed — so the stored credential's Issuer and TokenEndpoint cannot be re-verified.","triggerScenarios":"validateCredentialIssuer reaches the auth.GetAuthServerMetadata call (PRM discovery succeeded, Resource matches, AuthorizationServers non-empty) but the AS metadata fetch errors. Returns (false, wrapped error) — caller treats the credential as invalid with cause.","commonSituations":"AS metadata endpoint (/.well-known/oauth-authorization-server) temporarily down; AS rotated issuer URL not reflected in PRM; network/TLS failure to the AS; AS behind a proxy that 500s on the well-known path.","solutions":["Verify the AS issuer URL listed in authorization_servers[0] serves /.well-known/oauth-authorization-server.","Retry on transient AS outages; the credential is not cleared, so a later successful validation can restore reuse.","If the AS issuer changed, update the resource server's authorization_servers and clear the stored credential to force re-discovery."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// AS metadata fetch failed during credential validation — retry transient outages.\nasm, err := auth.GetAuthServerMetadata(ctx, prm.AuthorizationServers[0], h.client)\nif err != nil {\n    if isTransientNetworkErr(err) {\n        return false, nil // treat as 'cannot validate now'; keep credential, retry later\n    }\n    return false, fmt.Errorf(\"validate OAuth issuer: %w\", err)\n}","preventionTips":["Keep the AS issuer metadata endpoint highly available.","Do not treat a transient AS metadata fetch failure as a credential invalidation.","Update authorization_servers on the resource when the AS issuer rotates."],"tags":["oauth","mcp","validation","issuer","as-metadata","credential-reuse"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}