mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add endpoint and CLI for users to view their own OIDC claims (#23053)
- Adds a new API endpoint `GET /api/v2/users/oidc-claims` that returns only the **merged claims** (not the separate id_token/userinfo breakdown). Scoped exclusively to the authenticated user's own identity — no user parameter, so users cannot view each other's claims. - Adds a new CLI command:** `coder users oidc-claims` that hits the above endpoint. - The existing owner-only debug endpoint is preserved unchanged for admins who need the full claim breakdown. > 🤖 This PR was created with the help of Coder Agents, and will be reviewed by my human. 🧑💻
This commit is contained in:
Generated
+35
@@ -7870,6 +7870,31 @@ const docTemplate = `{
|
||||
]
|
||||
}
|
||||
},
|
||||
"/users/oidc-claims": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"summary": "Get OIDC claims for the authenticated user",
|
||||
"operationId": "get-oidc-claims-for-the-authenticated-user",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.OIDCClaimsResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/users/oidc/callback": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -16886,6 +16911,16 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.OIDCClaimsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"claims": {
|
||||
"description": "Claims are the merged claims from the OIDC provider. These\nare the union of the ID token claims and the userinfo claims,\nwhere userinfo claims take precedence on conflict.",
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.OIDCConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
Generated
+31
@@ -6965,6 +6965,27 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/users/oidc-claims": {
|
||||
"get": {
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Users"],
|
||||
"summary": "Get OIDC claims for the authenticated user",
|
||||
"operationId": "get-oidc-claims-for-the-authenticated-user",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.OIDCClaimsResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/users/oidc/callback": {
|
||||
"get": {
|
||||
"tags": ["Users"],
|
||||
@@ -15337,6 +15358,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.OIDCClaimsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"claims": {
|
||||
"description": "Claims are the merged claims from the OIDC provider. These\nare the union of the ID token claims and the userinfo claims,\nwhere userinfo claims take precedence on conflict.",
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.OIDCConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -1496,6 +1496,7 @@ func New(options *Options) *API {
|
||||
r.Post("/", api.postUser)
|
||||
r.Get("/", api.users)
|
||||
r.Post("/logout", api.postLogout)
|
||||
r.Get("/oidc-claims", api.userOIDCClaims)
|
||||
// These routes query information about site wide roles.
|
||||
r.Route("/roles", func(r chi.Router) {
|
||||
r.Get("/", api.AssignableSiteRoles)
|
||||
|
||||
@@ -72,6 +72,64 @@ func (api *API) userDebugOIDC(rw http.ResponseWriter, r *http.Request) {
|
||||
httpapi.Write(ctx, rw, http.StatusOK, link.Claims)
|
||||
}
|
||||
|
||||
// Returns the merged OIDC claims for the authenticated user.
|
||||
//
|
||||
// @Summary Get OIDC claims for the authenticated user
|
||||
// @ID get-oidc-claims-for-the-authenticated-user
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags Users
|
||||
// @Success 200 {object} codersdk.OIDCClaimsResponse
|
||||
// @Router /users/oidc-claims [get]
|
||||
func (api *API) userOIDCClaims(rw http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
apiKey = httpmw.APIKey(r)
|
||||
)
|
||||
|
||||
user, err := api.Database.GetUserByID(ctx, apiKey.UserID)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to get user.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if user.LoginType != database.LoginTypeOIDC {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "User is not an OIDC user.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
//nolint:gocritic // GetUserLinkByUserIDLoginType requires reading
|
||||
// rbac.ResourceSystem. The endpoint is scoped to the authenticated
|
||||
// user's own identity via apiKey, so this is safe.
|
||||
link, err := api.Database.GetUserLinkByUserIDLoginType(
|
||||
dbauthz.AsSystemRestricted(ctx),
|
||||
database.GetUserLinkByUserIDLoginTypeParams{
|
||||
UserID: user.ID,
|
||||
LoginType: database.LoginTypeOIDC,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to get user link.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
claims := link.Claims.MergedClaims
|
||||
if claims == nil {
|
||||
claims = map[string]interface{}{}
|
||||
}
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.OIDCClaimsResponse{
|
||||
Claims: claims,
|
||||
})
|
||||
}
|
||||
|
||||
// Returns whether the initial user has been created or not.
|
||||
//
|
||||
// @Summary Check initial user created
|
||||
|
||||
Reference in New Issue
Block a user