feat: Add multiple context and editor hooks to ChatInput component.
This commit is contained in:
parent
9774dea4b6
commit
08a92ba2d0
44
api/api.go
44
api/api.go
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user