diff --git a/openapi/oauth/token.go b/openapi/oauth/token.go index 7c336494..6d2f3658 100644 --- a/openapi/oauth/token.go +++ b/openapi/oauth/token.go @@ -253,8 +253,8 @@ func (s *Service) MakeAccessToken(clientID, scope, subject string, expiresIn int } // 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) +func (s *Service) MakeRefreshToken(clientID, scope, subject string, expiresIn ...int) (string, error) { + return s.generateRefreshToken(clientID, scope, subject, expiresIn...) } // Subject converts a userID to a subject using NanoID fingerprint @@ -415,14 +415,14 @@ func (s *Service) revokeAccessToken(accessToken string) error { } // generateRefreshToken generates and stores a new refresh token with scope and subject -func (s *Service) generateRefreshToken(clientID, scope, subject string) (string, error) { +func (s *Service) generateRefreshToken(clientID, scope, subject string, expiresIn ...int) (string, error) { refreshToken, err := s.generateToken("rfk", clientID) if err != nil { return "", err } // Store refresh token with metadata - err = s.storeRefreshTokenWithScope(refreshToken, clientID, scope, subject) + err = s.storeRefreshTokenWithScope(refreshToken, clientID, scope, subject, expiresIn...) if err != nil { return "", err } @@ -529,7 +529,7 @@ func (s *Service) storeRefreshToken(refreshToken, clientID string) error { } // storeRefreshTokenWithScope stores refresh token with metadata including scope and subject -func (s *Service) storeRefreshTokenWithScope(refreshToken, clientID, scope, subject string) error { +func (s *Service) storeRefreshTokenWithScope(refreshToken, clientID, scope, subject string, expiresIn ...int) error { tokenData := map[string]interface{}{ "client_id": clientID, "scope": scope, @@ -538,7 +538,12 @@ func (s *Service) storeRefreshTokenWithScope(refreshToken, clientID, scope, subj "issued_at": time.Now().Unix(), } - return s.store.Set(s.refreshTokenKey(refreshToken), tokenData, s.config.Token.RefreshTokenLifetime) + expires := s.config.Token.RefreshTokenLifetime + if len(expiresIn) > 0 && expiresIn[0] > 0 { + expires = time.Duration(expiresIn[0]) * time.Second + } + + return s.store.Set(s.refreshTokenKey(refreshToken), tokenData, expires) } // getRefreshTokenData retrieves refresh token data diff --git a/openapi/signin/api.go b/openapi/signin/api.go index e624c76c..123cac27 100644 --- a/openapi/signin/api.go +++ b/openapi/signin/api.go @@ -214,7 +214,18 @@ func authback(c *gin.Context) { return } - response.RespondWithSuccess(c, response.StatusOK, loginResponse) + // Authorize Cookie + accessToken := fmt.Sprintf("%s %s", loginResponse.TokenType, loginResponse.AccessToken) + refreshToken := fmt.Sprintf("%s %s", loginResponse.TokenType, loginResponse.RefreshToken) + + // Send Cookie + expires := time.Now().Add(time.Duration(loginResponse.ExpiresIn) * time.Second) + refreshExpires := time.Now().Add(time.Duration(loginResponse.RefreshTokenExpiresIn) * time.Second) + response.SendAccessTokenCookieWithExpiry(c, accessToken, expires) + response.SendRefreshTokenCookieWithExpiry(c, refreshToken, refreshExpires) + + // Send IDToken to the client + response.RespondWithSuccess(c, response.StatusOK, map[string]interface{}{"id_token": loginResponse.IDToken}) } // getOAuthAuthorizationURL generates OAuth authorization URL for a provider diff --git a/openapi/signin/login.go b/openapi/signin/login.go index a0ccc820..c26930a6 100644 --- a/openapi/signin/login.go +++ b/openapi/signin/login.go @@ -119,17 +119,18 @@ func LoginByUserID(userid string, ip string) (*LoginResponse, error) { } // Refresh Token - refreshToken, err := oauth.OAuth.MakeRefreshToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), subject) + refreshToken, err := oauth.OAuth.MakeRefreshToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), subject, yaoClientConfig.RefreshTokenExpiresIn) if err != nil { return nil, err } return &LoginResponse{ - AccessToken: accessToken, - IDToken: oidcToken, - RefreshToken: refreshToken, - ExpiresIn: yaoClientConfig.ExpiresIn, - TokenType: "Bearer", - Scope: strings.Join(scopes, " "), + AccessToken: accessToken, + IDToken: oidcToken, + RefreshToken: refreshToken, + ExpiresIn: yaoClientConfig.ExpiresIn, + RefreshTokenExpiresIn: yaoClientConfig.RefreshTokenExpiresIn, + TokenType: "Bearer", + Scope: strings.Join(scopes, " "), }, nil } diff --git a/openapi/signin/signin.go b/openapi/signin/signin.go index 8e73df7f..c7dee71f 100644 --- a/openapi/signin/signin.go +++ b/openapi/signin/signin.go @@ -156,7 +156,8 @@ func registerClient(clientID string) (*YaoClientConfig, error) { var clientConfig *YaoClientConfig = &YaoClientConfig{} clientConfig.ClientID = response.ClientID clientConfig.ClientSecret = response.ClientSecret - clientConfig.ExpiresIn = 3600 * 24 // 24 hours + clientConfig.ExpiresIn = 3600 * 24 // 24 hours + clientConfig.RefreshTokenExpiresIn = 3600 * 24 * 30 // 30 days clientConfig.Scopes = []string{"openid", "profile", "email"} return clientConfig, nil } diff --git a/openapi/signin/types.go b/openapi/signin/types.go index 0295b003..6cd9a0e5 100644 --- a/openapi/signin/types.go +++ b/openapi/signin/types.go @@ -66,10 +66,11 @@ type RegisterConfig struct { // YaoClientConfig represents the Yao OpenAPI Client config type YaoClientConfig struct { - ClientID string `json:"client_id,omitempty"` - ClientSecret string `json:"client_secret,omitempty"` - Scopes []string `json:"scopes,omitempty"` // Default scopes if not set in the provider config - ExpiresIn int `json:"expires_in,omitempty"` // Default expires in for the access token (optional) in seconds + ClientID string `json:"client_id,omitempty"` + ClientSecret string `json:"client_secret,omitempty"` + Scopes []string `json:"scopes,omitempty"` // Default scopes if not set in the provider config + ExpiresIn int `json:"expires_in,omitempty"` // Default expires in for the access token (optional) in seconds + RefreshTokenExpiresIn int `json:"refresh_token_expires_in,omitempty"` // Default expires in for the refresh token (optional) in seconds } // Provider represents a third party login provider @@ -161,12 +162,13 @@ 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"` + AccessToken string `json:"access_token"` + IDToken string `json:"id_token,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ExpiresIn int `json:"expires_in,omitempty"` + RefreshTokenExpiresIn int `json:"refresh_token_expires_in,omitempty"` + TokenType string `json:"token_type,omitempty"` + Scope string `json:"scope,omitempty"` } // Built-in preset mapping types