Implement ID token signing and user fingerprint management in OAuth
- Added `SignIDToken` method to the OAuth service for signing ID tokens with user data and claims. - Introduced `MakeAccessToken` and `MakeRefreshToken` methods for generating access and refresh tokens. - Implemented user fingerprint management with `Subject` and `UserID` methods for mapping user IDs to unique NanoIDs. - Enhanced `LoginByUserID` to generate and return OIDC tokens and access tokens during user login. - Updated `OIDCUserInfo` mapping to include additional user attributes and improved handling of optional fields.
This commit is contained in:
parent
392b6bee1b
commit
2b7040c834
5 changed files with 565 additions and 28 deletions
|
|
@ -457,6 +457,125 @@ func (s *Service) VerifyToken(token string) (*types.TokenClaims, error) {
|
|||
return s.verifyOpaqueToken(token)
|
||||
}
|
||||
|
||||
// SignIDToken signs an ID token with specific parameters and stores it
|
||||
func (s *Service) SignIDToken(clientID, scope string, expiresIn int, userdata *types.OIDCUserInfo) (string, error) {
|
||||
if s.signingCerts == nil || s.signingCerts.SigningKey == nil {
|
||||
return "", fmt.Errorf("signing certificates not initialized")
|
||||
}
|
||||
|
||||
// Check if userdata and subject are provided
|
||||
if userdata == nil || userdata.Sub == "" {
|
||||
return "", fmt.Errorf("userdata or userdata.Sub is required for ID token")
|
||||
}
|
||||
|
||||
tokenSubject := userdata.Sub
|
||||
|
||||
now := time.Now()
|
||||
|
||||
// Create OIDC ID Token claims
|
||||
idTokenClaims := &types.OIDCIDToken{
|
||||
// Required ID Token claims
|
||||
Iss: s.config.IssuerURL,
|
||||
Sub: tokenSubject,
|
||||
Aud: clientID,
|
||||
Exp: now.Add(time.Duration(expiresIn) * time.Second).Unix(),
|
||||
Iat: now.Unix(),
|
||||
}
|
||||
|
||||
// Create a map for custom claims that includes both standard and user info
|
||||
claims := jwt.MapClaims{
|
||||
// Standard OIDC ID Token claims
|
||||
"iss": idTokenClaims.Iss,
|
||||
"sub": idTokenClaims.Sub,
|
||||
"aud": idTokenClaims.Aud,
|
||||
"exp": idTokenClaims.Exp,
|
||||
"iat": idTokenClaims.Iat,
|
||||
"jti": generateJTI(),
|
||||
}
|
||||
|
||||
// Add user information from userdata
|
||||
// Add standard OIDC user claims if they exist
|
||||
if userdata.Name != "" {
|
||||
claims["name"] = userdata.Name
|
||||
}
|
||||
if userdata.GivenName != "" {
|
||||
claims["given_name"] = userdata.GivenName
|
||||
}
|
||||
if userdata.FamilyName != "" {
|
||||
claims["family_name"] = userdata.FamilyName
|
||||
}
|
||||
if userdata.MiddleName != "" {
|
||||
claims["middle_name"] = userdata.MiddleName
|
||||
}
|
||||
if userdata.Nickname != "" {
|
||||
claims["nickname"] = userdata.Nickname
|
||||
}
|
||||
if userdata.PreferredUsername != "" {
|
||||
claims["preferred_username"] = userdata.PreferredUsername
|
||||
}
|
||||
if userdata.Profile != "" {
|
||||
claims["profile"] = userdata.Profile
|
||||
}
|
||||
if userdata.Picture != "" {
|
||||
claims["picture"] = userdata.Picture
|
||||
}
|
||||
if userdata.Website != "" {
|
||||
claims["website"] = userdata.Website
|
||||
}
|
||||
if userdata.Email != "" {
|
||||
claims["email"] = userdata.Email
|
||||
}
|
||||
if userdata.EmailVerified != nil {
|
||||
claims["email_verified"] = *userdata.EmailVerified
|
||||
}
|
||||
if userdata.Gender != "" {
|
||||
claims["gender"] = userdata.Gender
|
||||
}
|
||||
if userdata.Birthdate != "" {
|
||||
claims["birthdate"] = userdata.Birthdate
|
||||
}
|
||||
if userdata.Zoneinfo != "" {
|
||||
claims["zoneinfo"] = userdata.Zoneinfo
|
||||
}
|
||||
if userdata.Locale != "" {
|
||||
claims["locale"] = userdata.Locale
|
||||
}
|
||||
if userdata.PhoneNumber != "" {
|
||||
claims["phone_number"] = userdata.PhoneNumber
|
||||
}
|
||||
if userdata.PhoneNumberVerified != nil {
|
||||
claims["phone_number_verified"] = *userdata.PhoneNumberVerified
|
||||
}
|
||||
if userdata.Address != nil {
|
||||
claims["address"] = userdata.Address
|
||||
}
|
||||
if userdata.UpdatedAt != nil {
|
||||
claims["updated_at"] = *userdata.UpdatedAt
|
||||
}
|
||||
|
||||
// Add scope if provided (useful for determining which claims to include)
|
||||
if scope != "" {
|
||||
claims["scope"] = scope
|
||||
}
|
||||
|
||||
// Create token with claims
|
||||
token := jwt.NewWithClaims(getSigningMethod(s.config.Token.AccessTokenSigningAlg), claims)
|
||||
|
||||
// Set key ID in header
|
||||
token.Header["kid"] = s.GetKeyID()
|
||||
|
||||
// Set token type in header
|
||||
token.Header["typ"] = "JWT"
|
||||
|
||||
// Sign token with private key
|
||||
signedToken, err := token.SignedString(s.signingCerts.SigningKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to sign ID token: %w", err)
|
||||
}
|
||||
|
||||
return signedToken, nil
|
||||
}
|
||||
|
||||
// signJWTToken signs a JWT token using the configured signing algorithm
|
||||
func (s *Service) signJWTToken(tokenType, clientID, scope, subject string, expiresIn int) (string, error) {
|
||||
if s.signingCerts == nil || s.signingCerts.SigningKey == nil {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
gonanoid "github.com/matoous/go-nanoid/v2"
|
||||
"github.com/yaoapp/yao/openapi/oauth/types"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
|
@ -242,7 +243,79 @@ func (s *Service) ValidateTokenBinding(ctx context.Context, token string, bindin
|
|||
return result, nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Public Token helper methods for internal use
|
||||
// ============================================================================
|
||||
|
||||
// MakeAccessToken generates a new access token with specific parameters and stores it
|
||||
func (s *Service) MakeAccessToken(clientID, scope, subject string, expiresIn int) (string, error) {
|
||||
return s.generateAccessTokenWithScope(clientID, scope, subject, expiresIn)
|
||||
}
|
||||
|
||||
// MakeRefreshToken generates a new refresh token with specific parameters and stores it
|
||||
func (s *Service) MakeRefreshToken(clientID, scope, subject string) (string, error) {
|
||||
return s.generateRefreshToken(clientID, scope, subject)
|
||||
}
|
||||
|
||||
// Subject converts a userID to a subject using NanoID fingerprint
|
||||
func (s *Service) Subject(clientID, userID string) (string, error) {
|
||||
// Check if mapping already exists for this clientID+userID
|
||||
mappingKey := s.userMappingKey(clientID, userID)
|
||||
if existingNanoID, exists := s.store.Get(mappingKey); exists {
|
||||
if nanoIDStr, ok := existingNanoID.(string); ok {
|
||||
return nanoIDStr, nil
|
||||
}
|
||||
}
|
||||
|
||||
maxRetries := 5
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
// Generate 12-character NanoID
|
||||
nanoID, err := generateNanoID(12)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate NanoID: %w", err)
|
||||
}
|
||||
|
||||
// Check if this NanoID already exists for this client
|
||||
key := s.userFingerprintKey(clientID, nanoID)
|
||||
_, exists := s.store.Get(key)
|
||||
if !exists {
|
||||
// Store both mappings
|
||||
// 1. clientID:nanoID -> userID
|
||||
if err := s.store.Set(key, userID, 0); err != nil {
|
||||
return "", fmt.Errorf("failed to store user fingerprint: %w", err)
|
||||
}
|
||||
// 2. clientID:userID -> nanoID (for checking existing mapping)
|
||||
if err := s.store.Set(mappingKey, nanoID, 0); err != nil {
|
||||
return "", fmt.Errorf("failed to store user mapping: %w", err)
|
||||
}
|
||||
return nanoID, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("failed to generate unique NanoID after %d retries", maxRetries)
|
||||
}
|
||||
|
||||
// UserID converts a subject to a userID using fingerprint lookup
|
||||
func (s *Service) UserID(clientID, subject string) (string, error) {
|
||||
key := s.userFingerprintKey(clientID, subject)
|
||||
userID, exists := s.store.Get(key)
|
||||
if !exists {
|
||||
return "", fmt.Errorf("fingerprint not found")
|
||||
}
|
||||
|
||||
userIDStr, ok := userID.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid userID format")
|
||||
}
|
||||
|
||||
return userIDStr, nil
|
||||
}
|
||||
|
||||
// MakeAuthorizationCode generates a new authorization code with specific parameters and stores it
|
||||
|
||||
// ============================================================================
|
||||
// Helper methods
|
||||
// ============================================================================
|
||||
|
||||
// validateAudience validates if an audience is valid
|
||||
func (s *Service) validateAudience(audience string) error {
|
||||
|
|
@ -520,6 +593,16 @@ func (s *Service) accessTokenKey(accessToken string) string {
|
|||
return fmt.Sprintf("%soauth:access_token:%s", s.prefix, accessToken)
|
||||
}
|
||||
|
||||
// userFingerprintKey generates a key for user fingerprint storage
|
||||
func (s *Service) userFingerprintKey(clientID, nanoID string) string {
|
||||
return fmt.Sprintf("%soauth:user_fingerprint:%s:%s", s.prefix, clientID, nanoID)
|
||||
}
|
||||
|
||||
// userMappingKey generates a key for reverse user mapping (clientID+userID -> nanoID)
|
||||
func (s *Service) userMappingKey(clientID, userID string) string {
|
||||
return fmt.Sprintf("%soauth:user_mapping:%s:%s", s.prefix, clientID, userID)
|
||||
}
|
||||
|
||||
// generateExchangedToken generates a new token for token exchange
|
||||
func (s *Service) generateExchangedToken(subjectToken string, audience string) (string, error) {
|
||||
// Extract token prefix for tracking purposes
|
||||
|
|
@ -556,3 +639,21 @@ func (s *Service) generateToken(tokenType string, clientID string) (string, erro
|
|||
|
||||
return fmt.Sprintf("%s_%s_%s_%s", tokenType, clientID, timestamp, randomPart), nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// User Fingerprint Methods
|
||||
// ============================================================================
|
||||
|
||||
// generateNanoID generates a Nano ID using the library
|
||||
func generateNanoID(length int) (string, error) {
|
||||
// URL-safe alphabet (no ambiguous characters like 0/O, 1/l/I)
|
||||
const alphabet = "23456789ABCDEFGHJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz"
|
||||
return gonanoid.Generate(alphabet, length)
|
||||
}
|
||||
|
||||
// DeleteUserFingerprint removes a fingerprint mapping
|
||||
func (s *Service) DeleteUserFingerprint(clientID, nanoID string) error {
|
||||
key := s.userFingerprintKey(clientID, nanoID)
|
||||
s.store.Del(key)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,304 @@
|
|||
package types
|
||||
|
||||
// Map converts the OIDCUserInfo to a map[string]interface{}
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Map converts the OIDCUserInfo to a map[string]interface{}, excluding empty values
|
||||
func (user OIDCUserInfo) Map() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"sub": user.Sub,
|
||||
"name": user.Name,
|
||||
"given_name": user.GivenName,
|
||||
"family_name": user.FamilyName,
|
||||
"middle_name": user.MiddleName,
|
||||
"nickname": user.Nickname,
|
||||
"preferred_username": user.PreferredUsername,
|
||||
"profile": user.Profile,
|
||||
"picture": user.Picture,
|
||||
"website": user.Website,
|
||||
"email": user.Email,
|
||||
"email_verified": user.EmailVerified,
|
||||
"gender": user.Gender,
|
||||
result := make(map[string]interface{})
|
||||
|
||||
// Only add non-empty string fields
|
||||
if user.Sub != "" {
|
||||
result["sub"] = user.Sub
|
||||
}
|
||||
if user.Name != "" {
|
||||
result["name"] = user.Name
|
||||
}
|
||||
if user.GivenName != "" {
|
||||
result["given_name"] = user.GivenName
|
||||
}
|
||||
if user.FamilyName != "" {
|
||||
result["family_name"] = user.FamilyName
|
||||
}
|
||||
if user.MiddleName != "" {
|
||||
result["middle_name"] = user.MiddleName
|
||||
}
|
||||
if user.Nickname != "" {
|
||||
result["nickname"] = user.Nickname
|
||||
}
|
||||
if user.PreferredUsername != "" {
|
||||
result["preferred_username"] = user.PreferredUsername
|
||||
}
|
||||
if user.Profile != "" {
|
||||
result["profile"] = user.Profile
|
||||
}
|
||||
if user.Picture != "" {
|
||||
result["picture"] = user.Picture
|
||||
}
|
||||
if user.Website != "" {
|
||||
result["website"] = user.Website
|
||||
}
|
||||
if user.Email != "" {
|
||||
result["email"] = user.Email
|
||||
}
|
||||
if user.Gender != "" {
|
||||
result["gender"] = user.Gender
|
||||
}
|
||||
if user.Birthdate != "" {
|
||||
result["birthdate"] = user.Birthdate
|
||||
}
|
||||
if user.Zoneinfo != "" {
|
||||
result["zoneinfo"] = user.Zoneinfo
|
||||
}
|
||||
if user.Locale != "" {
|
||||
result["locale"] = user.Locale
|
||||
}
|
||||
if user.PhoneNumber != "" {
|
||||
result["phone_number"] = user.PhoneNumber
|
||||
}
|
||||
|
||||
// Only add non-nil boolean pointer fields
|
||||
if user.EmailVerified != nil {
|
||||
result["email_verified"] = user.EmailVerified
|
||||
}
|
||||
if user.PhoneNumberVerified != nil {
|
||||
result["phone_number_verified"] = user.PhoneNumberVerified
|
||||
}
|
||||
|
||||
// Convert and add UpdatedAt if not nil
|
||||
if converted := unixToMySQL(user.UpdatedAt); converted != nil {
|
||||
result["updated_at"] = converted
|
||||
}
|
||||
|
||||
// Add address if present and has content
|
||||
if user.Address != nil {
|
||||
addressMap := make(map[string]interface{})
|
||||
if user.Address.Formatted != "" {
|
||||
addressMap["formatted"] = user.Address.Formatted
|
||||
}
|
||||
if user.Address.StreetAddress != "" {
|
||||
addressMap["street_address"] = user.Address.StreetAddress
|
||||
}
|
||||
if user.Address.Locality != "" {
|
||||
addressMap["locality"] = user.Address.Locality
|
||||
}
|
||||
if user.Address.Region != "" {
|
||||
addressMap["region"] = user.Address.Region
|
||||
}
|
||||
if user.Address.PostalCode != "" {
|
||||
addressMap["postal_code"] = user.Address.PostalCode
|
||||
}
|
||||
if user.Address.Country != "" {
|
||||
addressMap["country"] = user.Address.Country
|
||||
}
|
||||
if len(addressMap) > 0 {
|
||||
result["address"] = addressMap
|
||||
}
|
||||
}
|
||||
|
||||
// Include raw data if available
|
||||
// if user.Raw != nil {
|
||||
// // Merge raw data, but let structured fields take precedence
|
||||
// for k, v := range user.Raw {
|
||||
// if _, exists := result[k]; !exists && v != nil && v != "" {
|
||||
// result[k] = v
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// MakeOIDCUserInfo creates a new OIDCUserInfo from a map[string]interface{}
|
||||
func MakeOIDCUserInfo(user map[string]interface{}) *OIDCUserInfo {
|
||||
userInfo := &OIDCUserInfo{
|
||||
Raw: user, // Store original response
|
||||
}
|
||||
|
||||
// String fields with safe type assertion
|
||||
if sub, ok := user["sub"].(string); ok {
|
||||
userInfo.Sub = sub
|
||||
}
|
||||
if name, ok := user["name"].(string); ok {
|
||||
userInfo.Name = name
|
||||
}
|
||||
if givenName, ok := user["given_name"].(string); ok {
|
||||
userInfo.GivenName = givenName
|
||||
}
|
||||
if familyName, ok := user["family_name"].(string); ok {
|
||||
userInfo.FamilyName = familyName
|
||||
}
|
||||
if middleName, ok := user["middle_name"].(string); ok {
|
||||
userInfo.MiddleName = middleName
|
||||
}
|
||||
if nickname, ok := user["nickname"].(string); ok {
|
||||
userInfo.Nickname = nickname
|
||||
}
|
||||
if preferredUsername, ok := user["preferred_username"].(string); ok {
|
||||
userInfo.PreferredUsername = preferredUsername
|
||||
}
|
||||
if profile, ok := user["profile"].(string); ok {
|
||||
userInfo.Profile = profile
|
||||
}
|
||||
if picture, ok := user["picture"].(string); ok {
|
||||
userInfo.Picture = picture
|
||||
}
|
||||
if website, ok := user["website"].(string); ok {
|
||||
userInfo.Website = website
|
||||
}
|
||||
if email, ok := user["email"].(string); ok {
|
||||
userInfo.Email = email
|
||||
}
|
||||
if gender, ok := user["gender"].(string); ok {
|
||||
userInfo.Gender = gender
|
||||
}
|
||||
if birthdate, ok := user["birthdate"].(string); ok {
|
||||
userInfo.Birthdate = birthdate
|
||||
}
|
||||
if zoneinfo, ok := user["zoneinfo"].(string); ok {
|
||||
userInfo.Zoneinfo = zoneinfo
|
||||
}
|
||||
if locale, ok := user["locale"].(string); ok {
|
||||
userInfo.Locale = locale
|
||||
}
|
||||
if phoneNumber, ok := user["phone_number"].(string); ok {
|
||||
userInfo.PhoneNumber = phoneNumber
|
||||
}
|
||||
|
||||
// Boolean pointer fields
|
||||
if emailVerified, ok := user["email_verified"].(bool); ok {
|
||||
userInfo.EmailVerified = &emailVerified
|
||||
}
|
||||
if phoneVerified, ok := user["phone_number_verified"].(bool); ok {
|
||||
userInfo.PhoneNumberVerified = &phoneVerified
|
||||
}
|
||||
|
||||
// Updated_at field
|
||||
if updatedAt, ok := user["updated_at"]; ok {
|
||||
if converted := toUnixTimestamp(updatedAt); converted != nil {
|
||||
if unixTime, ok := converted.(int64); ok {
|
||||
userInfo.UpdatedAt = &unixTime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Address field (nested object)
|
||||
if addressData, ok := user["address"].(map[string]interface{}); ok {
|
||||
address := &OIDCAddress{}
|
||||
if formatted, ok := addressData["formatted"].(string); ok {
|
||||
address.Formatted = formatted
|
||||
}
|
||||
if streetAddress, ok := addressData["street_address"].(string); ok {
|
||||
address.StreetAddress = streetAddress
|
||||
}
|
||||
if locality, ok := addressData["locality"].(string); ok {
|
||||
address.Locality = locality
|
||||
}
|
||||
if region, ok := addressData["region"].(string); ok {
|
||||
address.Region = region
|
||||
}
|
||||
if postalCode, ok := addressData["postal_code"].(string); ok {
|
||||
address.PostalCode = postalCode
|
||||
}
|
||||
if country, ok := addressData["country"].(string); ok {
|
||||
address.Country = country
|
||||
}
|
||||
userInfo.Address = address
|
||||
}
|
||||
|
||||
return userInfo
|
||||
}
|
||||
|
||||
// unixToMySQL converts interface{} to MySQL DATETIME string
|
||||
func unixToMySQL(val interface{}) interface{} {
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var unixTime int64
|
||||
switch v := val.(type) {
|
||||
case int64:
|
||||
unixTime = v
|
||||
case *int64:
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
unixTime = *v
|
||||
case int:
|
||||
unixTime = int64(v)
|
||||
case float64:
|
||||
unixTime = int64(v)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
return time.Unix(unixTime, 0).UTC().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
// mysqlToUnix converts interface{} to Unix timestamp
|
||||
func mysqlToUnix(val interface{}) interface{} {
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var dateTime string
|
||||
switch v := val.(type) {
|
||||
case string:
|
||||
dateTime = v
|
||||
case *string:
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
dateTime = *v
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
if dateTime == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Try MySQL DATETIME format
|
||||
if t, err := time.Parse("2006-01-02 15:04:05", dateTime); err == nil {
|
||||
return t.Unix()
|
||||
}
|
||||
|
||||
// Try ISO format as fallback
|
||||
if t, err := time.Parse("2006-01-02T15:04:05Z", dateTime); err == nil {
|
||||
return t.Unix()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// toUnixTimestamp converts any interface{} to Unix timestamp
|
||||
func toUnixTimestamp(val interface{}) interface{} {
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v := val.(type) {
|
||||
case int64:
|
||||
return v
|
||||
case *int64:
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
return *v
|
||||
case int:
|
||||
return int64(v)
|
||||
case float64:
|
||||
return int64(v)
|
||||
case string:
|
||||
// Handle MySQL DATETIME or ISO format
|
||||
return mysqlToUnix(v)
|
||||
case *string:
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
return mysqlToUnix(*v)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package signin
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/yaoapp/kun/log"
|
||||
"github.com/yaoapp/yao/openapi/oauth"
|
||||
|
|
@ -93,13 +94,46 @@ func LoginByUserID(userid string, ip string) (*LoginResponse, error) {
|
|||
log.Warn("Failed to update last login: %s", err.Error())
|
||||
}
|
||||
|
||||
var scopes []string
|
||||
if v, ok := user["scopes"].([]string); ok {
|
||||
scopes = v
|
||||
}
|
||||
|
||||
// Get Config form app.yao config ()
|
||||
clientID := "1234567890"
|
||||
oidcExpiresIn := 3600
|
||||
accessTokenExpiresIn := 3600
|
||||
|
||||
subject, err := oauth.OAuth.Subject(clientID, userid)
|
||||
if err != nil {
|
||||
log.Warn("Failed to store user fingerprint: %s", err.Error())
|
||||
}
|
||||
oidcUserInfo := oauthtypes.MakeOIDCUserInfo(user)
|
||||
oidcUserInfo.Sub = subject
|
||||
|
||||
// OIDC Token
|
||||
oidcToken, err := oauth.OAuth.SignIDToken(clientID, strings.Join(scopes, " "), oidcExpiresIn, oidcUserInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Access Token
|
||||
accessToken, err := oauth.OAuth.MakeAccessToken(clientID, strings.Join(scopes, " "), subject, accessTokenExpiresIn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Refresh Token
|
||||
refreshToken, err := oauth.OAuth.MakeRefreshToken(clientID, strings.Join(scopes, " "), subject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &LoginResponse{
|
||||
AccessToken: "mock_access_token",
|
||||
IDToken: "mock_id_token",
|
||||
RefreshToken: "mock_refresh_token",
|
||||
ExpiresIn: 3600,
|
||||
AccessToken: accessToken,
|
||||
IDToken: oidcToken,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresIn: accessTokenExpiresIn,
|
||||
TokenType: "Bearer",
|
||||
Scope: "openid profile email",
|
||||
User: user,
|
||||
Scope: strings.Join(scopes, " "),
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,13 +153,12 @@ type OIDCAddress = oauthtypes.OIDCAddress
|
|||
|
||||
// LoginResponse represents the response for login
|
||||
type LoginResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
IDToken string `json:"id_token,omitempty"`
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
ExpiresIn int `json:"expires_in,omitempty"`
|
||||
TokenType string `json:"token_type,omitempty"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
User map[string]interface{} `json:"user,omitempty"`
|
||||
AccessToken string `json:"access_token"`
|
||||
IDToken string `json:"id_token,omitempty"`
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
ExpiresIn int `json:"expires_in,omitempty"`
|
||||
TokenType string `json:"token_type,omitempty"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
}
|
||||
|
||||
// Built-in preset mapping types
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue