feat: Add multiple context and editor hooks to ChatInput component.

This commit is contained in:
Haitao Pan 2026-02-02 21:02:32 +08:00
parent 9774dea4b6
commit 08a92ba2d0
2 changed files with 67 additions and 24 deletions

View File

@ -2344,9 +2344,14 @@ func (h *handler) oauthCallback(c *gin.Context) {
return
}
// 1. Check if user exists by identity
if !profile.Verified {
respondError(c, http.StatusUnauthorized, "email_not_verified", "oauth email must be verified")
return
}
var user *store.User
ctx := c.Request.Context()
user, err := h.store.GetUserByEmail(ctx, profile.Email)
existingUser, err := h.store.GetUserByEmail(ctx, profile.Email)
if err != nil && !errors.Is(err, store.ErrUserNotFound) {
respondError(c, http.StatusInternalServerError, "store_error", "database error")
return
@ -2357,26 +2362,17 @@ func (h *handler) oauthCallback(c *gin.Context) {
user = &store.User{
Name: profile.Name,
Email: profile.Email,
EmailVerified: true, // Trusted provider
EmailVerified: true, // Trusted provider, verified above
Level: store.LevelUser,
Role: store.RoleUser,
Groups: []string{"User"},
Active: true,
}
if err := h.store.CreateUser(ctx, user); err != nil {
respondError(c, http.StatusInternalServerError, "user_creation_failed", "failed to create user")
return
}
// Track identity
identity := &store.Identity{
UserID: user.ID,
Provider: providerName,
ExternalID: profile.ID,
}
if err := h.store.CreateIdentity(ctx, identity); err != nil {
slog.Warn("failed to create identity record", "err", err, "userID", user.ID)
}
// Provision trial
trialExpiresAt := time.Now().UTC().Add(7 * 24 * time.Hour)
trial := &store.Subscription{
@ -2390,6 +2386,28 @@ func (h *handler) oauthCallback(c *gin.Context) {
Meta: map[string]any{"expiresAt": trialExpiresAt},
}
h.store.UpsertSubscription(ctx, trial)
} else {
user = existingUser
// Ensure user is verified if they logged in via OAuth
if !user.EmailVerified {
user.EmailVerified = true
if err := h.store.UpdateUser(ctx, user); err != nil {
slog.Warn("failed to update user verification status during oauth", "err", err, "userID", user.ID)
}
}
}
// Always ensure identity record exists (bind OAuth ID to User Email)
identity := &store.Identity{
UserID: user.ID,
Provider: providerName,
ExternalID: profile.ID,
}
if err := h.store.CreateIdentity(ctx, identity); err != nil {
// Only log error if it's not a "already exists" error
if !strings.Contains(err.Error(), "exists") {
slog.Warn("failed to create identity record during oauth binding", "err", err, "userID", user.ID)
}
}
// Create session or generate public token for frontend redirect

View File

@ -12,9 +12,10 @@ import (
// OAuthUserProfile represents the unified user profile info from OAuth providers.
type OAuthUserProfile struct {
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
Verified bool `json:"verified"`
}
// OAuthProvider defines the interface for different OAuth2 providers.
@ -95,13 +96,35 @@ func (p *GitHubProvider) FetchProfile(ctx context.Context, token *oauth2.Token)
if err == nil {
defer resp.Body.Close()
var emails []struct {
Email string `json:"email"`
Primary bool `json:"primary"`
Email string `json:"email"`
Primary bool `json:"primary"`
Verified bool `json:"verified"`
}
if err := json.NewDecoder(resp.Body).Decode(&emails); err == nil {
for _, e := range emails {
if e.Primary {
profile.Email = e.Email
profile.Verified = e.Verified
break
}
}
}
}
} else {
// If we got email from /user, we still want to know if it's verified.
// GitHub /user doesn't return verification status, usually it's better
// to always fetch from /user/emails for accuracy if verification is required.
resp, err := client.Get("https://api.github.com/user/emails")
if err == nil {
defer resp.Body.Close()
var emails []struct {
Email string `json:"email"`
Verified bool `json:"verified"`
}
if err := json.NewDecoder(resp.Body).Decode(&emails); err == nil {
for _, e := range emails {
if e.Email == profile.Email {
profile.Verified = e.Verified
break
}
}
@ -144,17 +167,19 @@ func (p *GoogleProvider) FetchProfile(ctx context.Context, token *oauth2.Token)
defer resp.Body.Close()
var user struct {
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
VerifiedEmail bool `json:"verified_email"`
}
if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
return nil, err
}
return &OAuthUserProfile{
ID: user.ID,
Email: user.Email,
Name: user.Name,
ID: user.ID,
Email: user.Email,
Name: user.Name,
Verified: user.VerifiedEmail,
}, nil
}