| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| package controller |
|
|
| import ( |
| "errors" |
| "net/http" |
| "strconv" |
| "strings" |
| "time" |
| "veloera/common" |
| "veloera/model" |
|
|
| "github.com/gin-contrib/sessions" |
| "github.com/gin-gonic/gin" |
| ) |
|
|
| |
| var oauthHTTPClient = &http.Client{ |
| Timeout: 5 * time.Second, |
| } |
|
|
| |
| type OAuthProvider string |
|
|
| const ( |
| ProviderGitHub OAuthProvider = "GitHub" |
| ProviderLinuxDO OAuthProvider = "Linux DO" |
| ProviderIDCFlare OAuthProvider = "IDC Flare" |
| ProviderOIDC OAuthProvider = "OIDC" |
| ProviderTelegram OAuthProvider = "Telegram" |
| ) |
|
|
| |
| type OAuthUser struct { |
| ID string |
| Username string |
| DisplayName string |
| Email string |
| Provider OAuthProvider |
| |
| TrustLevel int |
| Active bool |
| Silenced bool |
| } |
|
|
| |
| type OAuthConfig struct { |
| Enabled bool |
| ClientID string |
| ClientSecret string |
| |
| MinTrustLevel int |
| } |
|
|
| |
| func validateOAuthState(c *gin.Context) bool { |
| session := sessions.Default(c) |
| state := c.Query("state") |
|
|
| if state == "" || session.Get("oauth_state") == nil || state != session.Get("oauth_state").(string) { |
| respondWithError(c, http.StatusForbidden, "state is empty or not same") |
| return false |
| } |
| return true |
| } |
|
|
| |
| func respondWithError(c *gin.Context, statusCode int, message string) { |
| c.JSON(statusCode, gin.H{ |
| "success": false, |
| "message": message, |
| }) |
| } |
|
|
| |
| func respondWithSuccess(c *gin.Context, message string, data interface{}) { |
| response := gin.H{ |
| "success": true, |
| "message": message, |
| } |
| if data != nil { |
| response["data"] = data |
| } |
| c.JSON(http.StatusOK, response) |
| } |
|
|
| |
| func checkUserStatus(c *gin.Context, user *model.User) bool { |
| if user.Id == 0 { |
| respondWithError(c, http.StatusOK, "用户已注销") |
| return false |
| } |
|
|
| if user.Status != common.UserStatusEnabled { |
| respondWithError(c, http.StatusOK, "用户已被封禁") |
| return false |
| } |
|
|
| return true |
| } |
|
|
| |
| func generateUniqueUsername(prefix string) (string, error) { |
| baseUserId := model.GetMaxUserId() + 1 |
|
|
| for i := 0; i < 5; i++ { |
| username := prefix + "_" + strconv.Itoa(baseUserId+i) |
| |
| exists, err := model.CheckUserExistOrDeleted(username, "") |
| if err != nil { |
| return "", err |
| } |
| if !exists { |
| return username, nil |
| } |
| } |
|
|
| |
| return "", errors.New("failed to generate unique username after 5 attempts") |
| } |
|
|
| |
| func handleOAuthLogin(c *gin.Context, oauthUser *OAuthUser, config *OAuthConfig, |
| userExistsFunc func(string) bool, |
| fillUserFunc func(*model.User) error, |
| createUserFunc func(*model.User, *OAuthUser, int) error) { |
|
|
| |
| if !validateOAuthState(c) { |
| return |
| } |
|
|
| session := sessions.Default(c) |
| username := session.Get("username") |
| if username != nil { |
| |
| handleOAuthBind(c, oauthUser, config, userExistsFunc, fillUserFunc) |
| return |
| } |
|
|
| if !config.Enabled { |
| respondWithError(c, http.StatusOK, "管理员未开启通过 "+string(oauthUser.Provider)+" 登录以及注册") |
| return |
| } |
|
|
| user := &model.User{} |
|
|
| |
| if userExistsFunc(oauthUser.ID) { |
| err := fillUserFunc(user) |
| if err != nil { |
| respondWithError(c, http.StatusOK, err.Error()) |
| return |
| } |
|
|
| if !checkUserStatus(c, user) { |
| return |
| } |
| } else { |
| |
| if !common.RegisterEnabled { |
| respondWithError(c, http.StatusOK, "管理员关闭了新用户注册") |
| return |
| } |
|
|
| |
| affCode := session.Get("aff") |
| inviterId := 0 |
| if affCode != nil { |
| inviterId, _ = model.GetUserIdByAffCode(affCode.(string)) |
| } |
|
|
| err := createUserFunc(user, oauthUser, inviterId) |
| if err != nil { |
| respondWithError(c, http.StatusOK, err.Error()) |
| return |
| } |
|
|
| if !checkUserStatus(c, user) { |
| return |
| } |
| } |
|
|
| setupLogin(user, c) |
| } |
|
|
| |
| func handleOAuthBind(c *gin.Context, oauthUser *OAuthUser, config *OAuthConfig, |
| userExistsFunc func(string) bool, |
| fillUserFunc func(*model.User) error) { |
|
|
| if !config.Enabled { |
| respondWithError(c, http.StatusOK, "管理员未开启通过 "+string(oauthUser.Provider)+" 登录以及注册") |
| return |
| } |
|
|
| if userExistsFunc(oauthUser.ID) { |
| respondWithError(c, http.StatusOK, "该 "+string(oauthUser.Provider)+" 账户已被绑定") |
| return |
| } |
|
|
| session := sessions.Default(c) |
| id := session.Get("id") |
| user := &model.User{Id: id.(int)} |
|
|
| err := user.FillUserById() |
| if err != nil { |
| respondWithError(c, http.StatusOK, err.Error()) |
| return |
| } |
|
|
| |
| switch oauthUser.Provider { |
| case ProviderGitHub: |
| user.GitHubId = oauthUser.ID |
| case ProviderLinuxDO: |
| user.LinuxDOId = oauthUser.ID |
| case ProviderIDCFlare: |
| user.IDCFlareId = oauthUser.ID |
| case ProviderOIDC: |
| user.OidcId = oauthUser.ID |
| case ProviderTelegram: |
| user.TelegramId = oauthUser.ID |
| } |
|
|
| err = user.Update(false) |
| if err != nil { |
| respondWithError(c, http.StatusOK, err.Error()) |
| return |
| } |
|
|
| respondWithSuccess(c, "bind", nil) |
| } |
|
|
| |
| func createGitHubUser(user *model.User, oauthUser *OAuthUser, inviterId int) error { |
| user.GitHubId = oauthUser.ID |
| user.Email = oauthUser.Email |
| user.Role = common.RoleCommonUser |
| user.Status = common.UserStatusEnabled |
|
|
| if oauthUser.DisplayName != "" { |
| user.DisplayName = oauthUser.DisplayName |
| } else { |
| user.DisplayName = "GitHub User" |
| } |
|
|
| |
| username, err := generateUniqueUsername("github") |
| if err != nil { |
| return err |
| } |
|
|
| user.Username = username |
| return user.Insert(inviterId) |
| } |
|
|
| |
| func createLinuxDOUser(user *model.User, oauthUser *OAuthUser, inviterId int) error { |
| |
| if oauthUser.TrustLevel < common.LinuxDOMinimumTrustLevel { |
| return errors.New("信任等级未达到管理员设置的最低信任等级") |
| } |
|
|
| user.LinuxDOId = oauthUser.ID |
| user.DisplayName = oauthUser.DisplayName |
| user.Role = common.RoleCommonUser |
| user.Status = common.UserStatusEnabled |
|
|
| |
| username, err := generateUniqueUsername("linuxdo") |
| if err != nil { |
| return err |
| } |
|
|
| user.Username = username |
| return user.Insert(inviterId) |
| } |
|
|
| |
| func createIDCFlareUser(user *model.User, oauthUser *OAuthUser, inviterId int) error { |
| |
| if oauthUser.TrustLevel < common.IDCFlareMinimumTrustLevel { |
| return errors.New("信任等级未达到管理员设置的最低信任等级") |
| } |
|
|
| user.IDCFlareId = oauthUser.ID |
| user.DisplayName = oauthUser.DisplayName |
| user.Role = common.RoleCommonUser |
| user.Status = common.UserStatusEnabled |
|
|
| |
| username, err := generateUniqueUsername("idcflare") |
| if err != nil { |
| return err |
| } |
|
|
| user.Username = username |
| return user.Insert(inviterId) |
| } |
|
|
| |
| func createOIDCUser(user *model.User, oauthUser *OAuthUser, inviterId int) error { |
| user.OidcId = oauthUser.ID |
| user.Email = oauthUser.Email |
| user.Role = common.RoleCommonUser |
| user.Status = common.UserStatusEnabled |
|
|
| if oauthUser.DisplayName != "" { |
| user.DisplayName = oauthUser.DisplayName |
| } else { |
| user.DisplayName = "OIDC User" |
| } |
|
|
| |
| if oauthUser.Username != "" { |
| user.Username = oauthUser.Username |
| err := user.Insert(inviterId) |
| if err != nil && strings.Contains(err.Error(), "UNIQUE constraint failed: users.username") { |
| |
| username, genErr := generateUniqueUsername("oidc") |
| if genErr != nil { |
| return genErr |
| } |
| user.Username = username |
| return user.Insert(inviterId) |
| } |
| return err |
| } else { |
| |
| username, err := generateUniqueUsername("oidc") |
| if err != nil { |
| return err |
| } |
| user.Username = username |
| return user.Insert(inviterId) |
| } |
| } |
|
|