mirror of
https://github.com/netbirdio/dex.git
synced 2026-05-22 18:43:53 -07:00
6f2e233c7a
Signed-off-by: maksim.nabokikh <max.nabokih@gmail.com>
164 lines
4.1 KiB
Go
164 lines
4.1 KiB
Go
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"math/big"
|
|
"net/http"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/coreos/go-oidc/v3/oidc"
|
|
"golang.org/x/oauth2"
|
|
|
|
"github.com/dexidp/dex/examples/example-app/session"
|
|
)
|
|
|
|
// handleTokenRefresh redeems a refresh token for a new token set.
|
|
func (s *Server) handleTokenRefresh(w http.ResponseWriter, r *http.Request) {
|
|
refresh := r.FormValue("refresh_token")
|
|
if refresh == "" {
|
|
http.Error(w, fmt.Sprintf("no refresh_token in request: %q", r.Form), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
ctx := oidc.ClientContext(r.Context(), s.client)
|
|
|
|
t := &oauth2.Token{
|
|
RefreshToken: refresh,
|
|
Expiry: time.Now().Add(-time.Hour),
|
|
}
|
|
token, err := s.oauth2Config(nil).TokenSource(ctx, t).Token()
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("failed to get token: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
s.renderTokenResult(w, r, token)
|
|
}
|
|
|
|
// renderTokenResult verifies an ID token, extracts claims, and renders the token page.
|
|
func (s *Server) renderTokenResult(w http.ResponseWriter, r *http.Request, token *oauth2.Token) {
|
|
rawIDToken, ok := token.Extra("id_token").(string)
|
|
if !ok {
|
|
http.Error(w, "no id_token in token response", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
idToken, err := s.verifier.Verify(r.Context(), rawIDToken)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("failed to verify ID token: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
accessToken, ok := token.Extra("access_token").(string)
|
|
if !ok {
|
|
accessToken = token.AccessToken
|
|
if accessToken == "" {
|
|
http.Error(w, "no access_token in token response", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Persist claims for session-aware index page and logout.
|
|
var uc session.UserClaims
|
|
_ = idToken.Claims(&uc)
|
|
s.auth.Set(&uc, rawIDToken)
|
|
|
|
claims, err := encodeIDTokenClaims(idToken)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
s.renderer.RenderTokenPage(w, TokenPageData{
|
|
IDToken: rawIDToken,
|
|
IDTokenJWTLink: jwtIOLink(rawIDToken),
|
|
AccessToken: accessToken,
|
|
AccessTokenJWTLink: jwtIOLink(accessToken),
|
|
RefreshToken: token.RefreshToken,
|
|
RedirectURL: s.redirectURI,
|
|
Claims: claims,
|
|
PublicKeyPEM: s.fetchPublicKeyPEM(),
|
|
})
|
|
}
|
|
|
|
// encodeIDTokenClaims extracts and pretty-prints the claims from an ID token.
|
|
func encodeIDTokenClaims(idToken *oidc.IDToken) (string, error) {
|
|
var claims json.RawMessage
|
|
if err := idToken.Claims(&claims); err != nil {
|
|
return "", fmt.Errorf("error decoding ID token claims: %v", err)
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
if err := json.Indent(buf, claims, "", " "); err != nil {
|
|
return "", fmt.Errorf("error indenting ID token claims: %v", err)
|
|
}
|
|
return buf.String(), nil
|
|
}
|
|
|
|
// jwtIOLink creates a jwt.io debugger URL for the given token.
|
|
func jwtIOLink(token string) string {
|
|
return "https://jwt.io/#debugger-io?token=" + url.QueryEscape(token)
|
|
}
|
|
|
|
// fetchPublicKeyPEM fetches the provider's JWKS and returns the first RSA public key as PEM.
|
|
func (s *Server) fetchPublicKeyPEM() string {
|
|
if s.jwksURL == "" {
|
|
return ""
|
|
}
|
|
|
|
resp, err := s.client.Get(s.jwksURL)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var jwks struct {
|
|
Keys []json.RawMessage `json:"keys"`
|
|
}
|
|
if err := json.NewDecoder(resp.Body).Decode(&jwks); err != nil || len(jwks.Keys) == 0 {
|
|
return ""
|
|
}
|
|
|
|
var key struct {
|
|
N string `json:"n"`
|
|
E string `json:"e"`
|
|
Kty string `json:"kty"`
|
|
}
|
|
if err := json.Unmarshal(jwks.Keys[0], &key); err != nil || key.Kty != "RSA" {
|
|
return ""
|
|
}
|
|
|
|
nBytes, err1 := base64.RawURLEncoding.DecodeString(key.N)
|
|
eBytes, err2 := base64.RawURLEncoding.DecodeString(key.E)
|
|
if err1 != nil || err2 != nil {
|
|
return ""
|
|
}
|
|
|
|
var eInt int
|
|
for _, b := range eBytes {
|
|
eInt = eInt<<8 | int(b)
|
|
}
|
|
|
|
pubKey := &rsa.PublicKey{
|
|
N: new(big.Int).SetBytes(nBytes),
|
|
E: eInt,
|
|
}
|
|
|
|
pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
return string(pem.EncodeToMemory(&pem.Block{
|
|
Type: "PUBLIC KEY",
|
|
Bytes: pubKeyBytes,
|
|
}))
|
|
}
|