Merge ffe5bc1148 into 1c3fe4da3c
commit
e2a09536ac
@ -0,0 +1,89 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
type OIDCIDTokenClaims struct {
|
||||
jwt.RegisteredClaims
|
||||
Nonce string `json:"nonce,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
PreferredUsername string `json:"preferred_username,omitempty"`
|
||||
Picture string `json:"picture,omitempty"`
|
||||
UpdatedAt int64 `json:"updated_at,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
EmailVerified bool `json:"email_verified,omitempty"`
|
||||
}
|
||||
|
||||
type JWKSet struct {
|
||||
Keys []JWK `json:"keys"`
|
||||
}
|
||||
|
||||
type JWK struct {
|
||||
Kty string `json:"kty"`
|
||||
Use string `json:"use"`
|
||||
Alg string `json:"alg"`
|
||||
Kid string `json:"kid"`
|
||||
N string `json:"n"`
|
||||
E string `json:"e"`
|
||||
}
|
||||
|
||||
func SignOIDCIDToken(privateKeyRaw string, claims *OIDCIDTokenClaims) (string, error) {
|
||||
key, err := parseRSAPrivateKey(privateKeyRaw)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
||||
token.Header["kid"] = oidcSigningKeyID(&key.PublicKey)
|
||||
return token.SignedString(key)
|
||||
}
|
||||
|
||||
func OIDCJWKSet(privateKeyRaw string) (*JWKSet, error) {
|
||||
key, err := parseRSAPrivateKey(privateKeyRaw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kid := oidcSigningKeyID(&key.PublicKey)
|
||||
return &JWKSet{Keys: []JWK{buildJWK(&key.PublicKey, kid)}}, nil
|
||||
}
|
||||
|
||||
func parseRSAPrivateKey(privateKeyRaw string) (*rsa.PrivateKey, error) {
|
||||
block, _ := pem.Decode([]byte(privateKeyRaw))
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("invalid OIDC signing key PEM")
|
||||
}
|
||||
|
||||
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid OIDC signing key: %w", err)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func oidcSigningKeyID(key *rsa.PublicKey) string {
|
||||
der, _ := x509.MarshalPKIXPublicKey(key)
|
||||
sum := sha256.Sum256(der)
|
||||
return base64.RawURLEncoding.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func buildJWK(key *rsa.PublicKey, kid string) JWK {
|
||||
return JWK{
|
||||
Kty: "RSA",
|
||||
Use: "sig",
|
||||
Alg: "RS256",
|
||||
Kid: kid,
|
||||
N: base64.RawURLEncoding.EncodeToString(key.N.Bytes()),
|
||||
E: base64.RawURLEncoding.EncodeToString(big.NewInt(int64(key.E)).Bytes()),
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/application/constants"
|
||||
"github.com/cloudreve/Cloudreve/v4/application/dependency"
|
||||
"github.com/cloudreve/Cloudreve/v4/inventory/types"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/auth"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/cluster/routes"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type DiscoveryService struct{}
|
||||
|
||||
type JWKService struct{}
|
||||
|
||||
func (s *DiscoveryService) Get(c *gin.Context) *DiscoveryResponse {
|
||||
issuer := oidcIssuer(c)
|
||||
return &DiscoveryResponse{
|
||||
Issuer: issuer.String(),
|
||||
AuthorizationEndpoint: routes.MasterOIDCEndpointUrl(issuer, "/session/authorize"),
|
||||
TokenEndpoint: routes.MasterOIDCEndpointUrl(issuer, constants.APIPrefix+"/session/oauth/token"),
|
||||
UserInfoEndpoint: routes.MasterOIDCEndpointUrl(issuer, constants.APIPrefix+"/session/oauth/userinfo"),
|
||||
JWKSURI: routes.MasterOIDCEndpointUrl(issuer, constants.APIPrefix+"/session/oauth/jwks"),
|
||||
ResponseTypesSupported: []string{
|
||||
"code",
|
||||
},
|
||||
GrantTypesSupported: []string{
|
||||
"authorization_code",
|
||||
},
|
||||
SubjectTypesSupported: []string{
|
||||
"public",
|
||||
},
|
||||
IDTokenSigningAlgValuesSupported: []string{
|
||||
"RS256",
|
||||
},
|
||||
TokenEndpointAuthMethods: []string{
|
||||
"client_secret_post",
|
||||
},
|
||||
CodeChallengeMethodsSupported: []string{
|
||||
"S256",
|
||||
},
|
||||
ScopesSupported: []string{
|
||||
types.ScopeOpenID,
|
||||
types.ScopeProfile,
|
||||
types.ScopeEmail,
|
||||
},
|
||||
ClaimsSupported: []string{
|
||||
"sub",
|
||||
"name",
|
||||
"preferred_username",
|
||||
"picture",
|
||||
"updated_at",
|
||||
"email",
|
||||
"email_verified",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *JWKService) Get(c *gin.Context) (*auth.JWKSet, error) {
|
||||
dep := dependency.FromContext(c)
|
||||
return auth.OIDCJWKSet(dep.SettingProvider().OIDCSigningPrivateKey(c))
|
||||
}
|
||||
|
||||
func oidcIssuer(c *gin.Context) *url.URL {
|
||||
dep := dependency.FromContext(c)
|
||||
issuer := *dep.SettingProvider().SiteURL(c)
|
||||
issuer.RawQuery = ""
|
||||
issuer.Fragment = ""
|
||||
return &issuer
|
||||
}
|
||||
Loading…
Reference in new issue