From 6f3a57cb33237c2bb9f881aca2c228f974843e56 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 18 Sep 2025 10:10:58 +0800 Subject: [PATCH] Refactor login cookie handling to improve security and organization - Consolidated cookie management into a new SendLoginCookies function, which sends access token, refresh token, and session ID cookies with appropriate security settings. - Updated authback functions in the signin and oauth files to utilize the new SendLoginCookies function, enhancing code clarity and reducing duplication. --- openapi/signin/api.go | 36 ++++++++++++---- openapi/user/TODO.md | 96 +++++++++++++++++++++++++++++++++++++++++++ openapi/user/login.go | 27 ++++++++++++ openapi/user/oauth.go | 11 +---- 4 files changed, 152 insertions(+), 18 deletions(-) create mode 100644 openapi/user/TODO.md diff --git a/openapi/signin/api.go b/openapi/signin/api.go index 123cac27..650ba2a3 100644 --- a/openapi/signin/api.go +++ b/openapi/signin/api.go @@ -214,15 +214,8 @@ func authback(c *gin.Context) { return } - // 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 all login cookies (access token, refresh token, and session ID) + SendLoginCookies(c, loginResponse, sid) // Send IDToken to the client response.RespondWithSuccess(c, response.StatusOK, map[string]interface{}{"id_token": loginResponse.IDToken}) @@ -673,3 +666,28 @@ func isPrivateIPv6(ip net.IP) bool { } return false } + +// SendLoginCookies sends all necessary cookies for a successful login +// This includes access token, refresh token, and session ID cookies with appropriate security settings +func SendLoginCookies(c *gin.Context, loginResponse *LoginResponse, sessionID string) { + // Format tokens with Bearer prefix + accessToken := fmt.Sprintf("%s %s", loginResponse.TokenType, loginResponse.AccessToken) + refreshToken := fmt.Sprintf("%s %s", loginResponse.TokenType, loginResponse.RefreshToken) + + // Calculate expiration times + expires := time.Now().Add(time.Duration(loginResponse.ExpiresIn) * time.Second) + refreshExpires := time.Now().Add(time.Duration(loginResponse.RefreshTokenExpiresIn) * time.Second) + + // Send access token cookie + response.SendAccessTokenCookieWithExpiry(c, accessToken, expires) + + // Send refresh token cookie + response.SendRefreshTokenCookieWithExpiry(c, refreshToken, refreshExpires) + + // Send session ID cookie with the same expiration as access token + // Using HTTP-only flag for security + options := response.NewSecureCookieOptions(). + WithExpires(expires). + WithSameSite("Strict") + response.SendSecureCookieWithOptions(c, "session_id", sessionID, options) +} diff --git a/openapi/user/TODO.md b/openapi/user/TODO.md new file mode 100644 index 00000000..cdbb2146 --- /dev/null +++ b/openapi/user/TODO.md @@ -0,0 +1,96 @@ +# User Module TODO + +## ✅ Implemented (5/80) + +### Authentication + +- ✅ GET `/user/login` - Get login page configuration +- ✅ POST `/user/login` - User login + +### OAuth & Third-Party Integration + +- ✅ GET `/user/oauth/:provider/authorize` - Get OAuth authorization URL +- ✅ POST `/user/oauth/:provider/authorize/prepare` - Handle OAuth POST callback (Apple, WeChat) +- ✅ POST `/user/oauth/:provider/callback` - Handle OAuth GET callback (Google, GitHub) + +## ❌ TODO (75/80) + +### Authentication + +- ❌ POST `/user/register` - User registration +- ❌ POST `/user/logout` - User logout + +### Profile Management + +- ❌ GET `/user/profile` - Get user profile +- ❌ PUT `/user/profile` - Update user profile + +### Account Security (13 endpoints) + +- ❌ Password management (3 endpoints) +- ❌ Email management (5 endpoints) +- ❌ Mobile management (5 endpoints) + +### Multi-Factor Authentication (12 endpoints) + +- ❌ TOTP management (7 endpoints) +- ❌ SMS MFA management (5 endpoints) + +### OAuth & Third-Party Integration + +- ❌ GET `/user/oauth/providers` - Get linked OAuth providers +- ❌ DELETE `/user/oauth/:provider` - Unlink OAuth provider +- ❌ GET `/user/oauth/providers/available` - Get available OAuth providers +- ❌ POST `/user/oauth/:provider/connect` - Connect OAuth provider + +### API Keys Management (6 endpoints) + +- ❌ CRUD operations and regeneration for API keys + +### Credits & Top-up (6 endpoints) + +- ❌ Credits info, history, and top-up management + +### Subscription Management (2 endpoints) + +- ❌ Subscription info and updates + +### Usage Statistics (2 endpoints) + +- ❌ Usage statistics and history + +### Billing & Invoices (2 endpoints) + +- ❌ Billing history and invoice list + +### Referral & Invitations (4 endpoints) + +- ❌ Referral codes, statistics, history, commissions + +### Team Management (15 endpoints) + +- ❌ Team CRUD (5 endpoints) +- ❌ Member management (5 endpoints) +- ❌ Invitation management (5 endpoints) + +### Invitation Response (3 endpoints) + +- ❌ Cross-module invitation handling + +### User Preferences (3 endpoints) + +- ❌ User preference settings + +### Privacy Settings (3 endpoints) + +- ❌ Privacy settings + +### User Management (Admin) (5 endpoints) + +- ❌ User CRUD operations + +## Progress Summary + +- **Completion**: 6.25% (5/80) +- **Core Features**: Authentication and OAuth completed +- **Next Steps**: Recommend implementing basic user management (register, logout, profile) first diff --git a/openapi/user/login.go b/openapi/user/login.go index 7e47cbc9..961c27cd 100644 --- a/openapi/user/login.go +++ b/openapi/user/login.go @@ -2,7 +2,9 @@ package user import ( "context" + "fmt" "strings" + "time" "github.com/gin-gonic/gin" "github.com/yaoapp/gou/session" @@ -179,3 +181,28 @@ func LoginByUserID(userid string, ip string) (*LoginResponse, error) { func generateSessionID() string { return session.ID() } + +// SendLoginCookies sends all necessary cookies for a successful login +// This includes access token, refresh token, and session ID cookies with appropriate security settings +func SendLoginCookies(c *gin.Context, loginResponse *LoginResponse, sessionID string) { + // Format tokens with Bearer prefix + accessToken := fmt.Sprintf("%s %s", loginResponse.TokenType, loginResponse.AccessToken) + refreshToken := fmt.Sprintf("%s %s", loginResponse.TokenType, loginResponse.RefreshToken) + + // Calculate expiration times + expires := time.Now().Add(time.Duration(loginResponse.ExpiresIn) * time.Second) + refreshExpires := time.Now().Add(time.Duration(loginResponse.RefreshTokenExpiresIn) * time.Second) + + // Send access token cookie + response.SendAccessTokenCookieWithExpiry(c, accessToken, expires) + + // Send refresh token cookie + response.SendRefreshTokenCookieWithExpiry(c, refreshToken, refreshExpires) + + // Send session ID cookie with the same expiration as access token + // Using HTTP-only flag for security + options := response.NewSecureCookieOptions(). + WithExpires(expires). + WithSameSite("Strict") + response.SendSecureCookieWithOptions(c, "session_id", sessionID, options) +} diff --git a/openapi/user/oauth.go b/openapi/user/oauth.go index 4114829f..19a47ccc 100644 --- a/openapi/user/oauth.go +++ b/openapi/user/oauth.go @@ -184,15 +184,8 @@ func authback(c *gin.Context) { return } - // 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 all login cookies (access token, refresh token, and session ID) + SendLoginCookies(c, loginResponse, sid) // Send IDToken to the client response.RespondWithSuccess(c, response.StatusOK, map[string]interface{}{"id_token": loginResponse.IDToken})