mirror of
https://github.com/grafana/grafana.git
synced 2026-06-13 10:30:42 -04:00
90 lines
2.8 KiB
Go
90 lines
2.8 KiB
Go
package github
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v4"
|
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
|
)
|
|
|
|
const (
|
|
// JWTExpirationMinutes is the token expiration time for GitHub App JWT tokens
|
|
// Based on https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app
|
|
// The expiration time muse max 10 minutes.
|
|
JWTExpirationMinutes = 10
|
|
)
|
|
|
|
// GenerateJWTToken creates a GitHub App JWT token from appID and base64-encoded private key.
|
|
// The private key should be base64-encoded PEM format.
|
|
// Returns the signed JWT token string.
|
|
// related to how Github wants their token to be built.
|
|
func GenerateJWTToken(appID string, privateKey common.RawSecureValue) (common.RawSecureValue, error) {
|
|
// Decode base64-encoded private key
|
|
privateKeyPEM, err := base64.StdEncoding.DecodeString(string(privateKey))
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to decode base64 private key: %w", err)
|
|
}
|
|
|
|
// Parse the private key
|
|
key, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyPEM)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to parse private key: %w", err)
|
|
}
|
|
|
|
// Create the JWT token
|
|
now := time.Now()
|
|
claims := jwt.RegisteredClaims{
|
|
IssuedAt: jwt.NewNumericDate(now),
|
|
ExpiresAt: jwt.NewNumericDate(now.Add(time.Duration(JWTExpirationMinutes) * time.Minute)),
|
|
Issuer: appID,
|
|
}
|
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
|
signedToken, err := token.SignedString(key)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to sign JWT token: %w", err)
|
|
}
|
|
|
|
return common.RawSecureValue(signedToken), nil
|
|
}
|
|
|
|
func parseJWTToken(token, privateKey common.RawSecureValue) (*jwt.RegisteredClaims, error) {
|
|
privateKeyPEM, err := base64.StdEncoding.DecodeString(string(privateKey))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode base64 private key: %w", err)
|
|
}
|
|
|
|
key, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyPEM)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse private key: %w", err)
|
|
}
|
|
|
|
parser := jwt.NewParser(
|
|
jwt.WithValidMethods([]string{jwt.SigningMethodRS256.Alg()}),
|
|
jwt.WithoutClaimsValidation(),
|
|
)
|
|
parsedToken, err := parser.ParseWithClaims(string(token), &jwt.RegisteredClaims{}, func(_ *jwt.Token) (any, error) {
|
|
return &key.PublicKey, nil
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse token: %w", err)
|
|
}
|
|
|
|
claims, ok := parsedToken.Claims.(*jwt.RegisteredClaims)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unexpected token claims")
|
|
}
|
|
|
|
return claims, nil
|
|
}
|
|
|
|
func getIssuingAndExpirationTimeFromToken(token, privateKey common.RawSecureValue) (time.Time, time.Time, error) {
|
|
claims, err := parseJWTToken(token, privateKey)
|
|
if err != nil {
|
|
return time.Time{}, time.Time{}, fmt.Errorf("failed to parse token: %w", err)
|
|
}
|
|
|
|
return claims.IssuedAt.Time, claims.ExpiresAt.Time, nil
|
|
}
|