Files
OpenFlare/internal/apps/oauth/logics.go
T
2026-06-08 20:34:28 +08:00

153 lines
4.2 KiB
Go

/*
Copyright 2025 linux.do
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package oauth
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/linux-do/credit/internal/common"
"github.com/linux-do/credit/internal/config"
"github.com/linux-do/credit/internal/db"
"github.com/linux-do/credit/internal/model"
"github.com/linux-do/credit/internal/otel_trace"
"go.opentelemetry.io/otel/codes"
"gorm.io/gorm"
)
func GetUserIDFromSession(s sessions.Session) uint64 {
userID, ok := s.Get(UserIDKey).(uint64)
if !ok {
return 0
}
return userID
}
func GetUserIDFromContext(c *gin.Context) uint64 {
session := sessions.Default(c)
return GetUserIDFromSession(session)
}
// doOAuth 执行 OAuth2/OIDC 流程
func doOAuth(ctx context.Context, code string, nonce string) (*model.User, error) {
ctx, span := otel_trace.Start(ctx, "OAuth")
defer span.End()
var userInfo model.OAuthUserInfo
// 使用授权码换取 Token
token, err := oauthConf.Exchange(ctx, code)
if err != nil {
span.SetStatus(codes.Error, err.Error())
return nil, err
}
if oidcVerifier != nil {
if rawIDToken, ok := token.Extra("id_token").(string); ok {
idToken, verifyErr := oidcVerifier.Verify(ctx, rawIDToken)
if verifyErr != nil {
err := fmt.Errorf("%s: %w", IDTokenVerifyFailed, verifyErr)
span.SetStatus(codes.Error, err.Error())
return nil, err
}
if nonce != "" && idToken.Nonce != nonce {
span.SetStatus(codes.Error, NonceMismatch)
return nil, errors.New(NonceMismatch)
}
if claimsErr := idToken.Claims(&userInfo); claimsErr != nil {
span.SetStatus(codes.Error, claimsErr.Error())
return nil, claimsErr
}
}
}
if userInfo.GetID() == 0 {
client := oauthConf.Client(ctx, token)
resp, httpErr := client.Get(config.Config.OAuth2.UserEndpoint)
if httpErr != nil {
span.SetStatus(codes.Error, httpErr.Error())
return nil, httpErr
}
defer resp.Body.Close()
responseData, readErr := io.ReadAll(resp.Body)
if readErr != nil {
span.SetStatus(codes.Error, readErr.Error())
return nil, readErr
}
if unmarshalErr := json.Unmarshal(responseData, &userInfo); unmarshalErr != nil {
span.SetStatus(codes.Error, unmarshalErr.Error())
return nil, unmarshalErr
}
}
if !userInfo.Active {
err := errors.New(common.BannedAccount)
span.SetStatus(codes.Error, err.Error())
return nil, err
}
var user model.User
err = db.DB(ctx).Transaction(func(tx *gorm.DB) error {
var holder model.User
if conflictErr := tx.Where("username = ? AND id != ?", userInfo.Username, userInfo.GetID()).First(&holder).Error; conflictErr == nil {
// 存在冲突 -> 将占用者改名并注销
newParams := map[string]interface{}{
"username": fmt.Sprintf("%s已注销: %s", holder.Username, uuid.NewString()),
"is_active": false,
}
if updateErr := tx.Model(&holder).Updates(newParams).Error; updateErr != nil {
return updateErr
}
}
// 根据 ID 处理当前用户的 更新 或 创建
if queryErr := tx.Where("id = ?", userInfo.GetID()).First(&user).Error; queryErr == nil {
// 用户已存在 -> 更新信息
if activeErr := user.CheckActive(); activeErr != nil {
return activeErr
}
user.UpdateFromOAuthInfo(&userInfo)
if saveErr := tx.Save(&user).Error; saveErr != nil {
return saveErr
}
} else if errors.Is(queryErr, gorm.ErrRecordNotFound) {
// 用户不存在 -> 创建新用户
user = model.User{}
if createErr := user.CreateUser(tx, &userInfo); createErr != nil {
return createErr
}
} else {
return queryErr
}
return nil
})
if err != nil {
span.SetStatus(codes.Error, err.Error())
return nil, err
}
return &user, nil
}