support Nextcloud OAuth 2.0 authentication

This commit is contained in:
MaysWind
2025-10-21 01:52:28 +08:00
parent 600ae2bd58
commit 53a8ad71c6
74 changed files with 2046 additions and 241 deletions
+142 -3
View File
@@ -22,6 +22,7 @@ type AuthorizationsApi struct {
userAppCloudSettings *services.UserApplicationCloudSettingsService
tokens *services.TokenService
twoFactorAuthorizations *services.TwoFactorAuthorizationService
userExternalAuths *services.UserExternalAuthService
}
// Initialize a authorization api singleton instance
@@ -48,11 +49,16 @@ var (
userAppCloudSettings: services.UserApplicationCloudSettings,
tokens: services.Tokens,
twoFactorAuthorizations: services.TwoFactorAuthorizations,
userExternalAuths: services.UserExternalAuths,
}
)
// AuthorizeHandler verifies and authorizes current login request
func (a *AuthorizationsApi) AuthorizeHandler(c *core.WebContext) (any, *errs.Error) {
if !a.CurrentConfig().EnableInternalAuth {
return nil, errs.ErrCannotLoginByPassword
}
var credential models.UserLoginRequest
err := c.ShouldBindJSON(&credential)
@@ -151,7 +157,7 @@ func (a *AuthorizationsApi) AuthorizeHandler(c *core.WebContext) (any, *errs.Err
applicationCloudSettingSlice = &userApplicationCloudSettings.Settings
}
log.Infof(c, "[authorizations.AuthorizeHandler] user \"uid:%d\" has logined, token type is %d, token will be expired at %d", user.Uid, claims.Type, claims.ExpiresAt)
log.Infof(c, "[authorizations.AuthorizeHandler] user \"uid:%d\" has logged in, token type is %d, token will be expired at %d", user.Uid, claims.Type, claims.ExpiresAt)
authResp := a.getAuthResponse(c, token, twoFactorEnable, user, applicationCloudSettingSlice)
return authResp, nil
@@ -159,6 +165,10 @@ func (a *AuthorizationsApi) AuthorizeHandler(c *core.WebContext) (any, *errs.Err
// TwoFactorAuthorizeHandler verifies and authorizes current 2fa login by passcode
func (a *AuthorizationsApi) TwoFactorAuthorizeHandler(c *core.WebContext) (any, *errs.Error) {
if !a.CurrentConfig().EnableInternalAuth {
return nil, errs.ErrCannotLoginByPassword
}
var credential models.TwoFactorLoginRequest
err := c.ShouldBindJSON(&credential)
@@ -198,7 +208,7 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeHandler(c *core.WebContext) (any,
user, err := a.users.GetUserById(c, uid)
if err != nil {
log.Errorf(c, "[authorizations.TwoFactorAuthorizeHandler] failed to get user \"uid:%d\" info, because %s", user.Uid, err.Error())
log.Errorf(c, "[authorizations.TwoFactorAuthorizeHandler] failed to get user \"uid:%d\" info, because %s", uid, err.Error())
return nil, errs.ErrUserNotFound
}
@@ -246,6 +256,10 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeHandler(c *core.WebContext) (any,
// TwoFactorAuthorizeByRecoveryCodeHandler verifies and authorizes current 2fa login by recovery code
func (a *AuthorizationsApi) TwoFactorAuthorizeByRecoveryCodeHandler(c *core.WebContext) (any, *errs.Error) {
if !a.CurrentConfig().EnableInternalAuth {
return nil, errs.ErrCannotLoginByPassword
}
var credential models.TwoFactorRecoveryCodeLoginRequest
err := c.ShouldBindJSON(&credential)
@@ -276,7 +290,7 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeByRecoveryCodeHandler(c *core.WebC
user, err := a.users.GetUserById(c, uid)
if err != nil {
log.Errorf(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] failed to get user \"uid:%d\" info, because %s", user.Uid, err.Error())
log.Errorf(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] failed to get user \"uid:%d\" info, because %s", uid, err.Error())
return nil, errs.ErrUserNotFound
}
@@ -338,6 +352,131 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeByRecoveryCodeHandler(c *core.WebC
return authResp, nil
}
// OAuth2CallbackAuthorizeHandler verifies and authorizes current OAuth 2.0 callback login
func (a *AuthorizationsApi) OAuth2CallbackAuthorizeHandler(c *core.WebContext) (any, *errs.Error) {
if !a.CurrentConfig().EnableOAuth2Login {
return nil, errs.ErrOAuth2NotEnabled
}
var credential models.OAuth2CallbackLoginRequest
err := c.ShouldBindJSON(&credential)
if err != nil {
log.Warnf(c, "[authorizations.OAuth2CallbackAuthorizeHandler] parse request failed, because %s", err.Error())
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
}
userExternalAuthType := core.UserExternalAuthType(credential.Provider)
if !userExternalAuthType.IsValid() {
log.Warnf(c, "[authorizations.OAuth2CallbackAuthorizeHandler] provider \"%s\" is invalid", credential.Provider)
return nil, errs.ErrInvalidOAuth2Provider
}
uid := c.GetCurrentUid()
err = a.CheckFailureCount(c, uid)
if err != nil {
log.Warnf(c, "[authorizations.OAuth2CallbackAuthorizeHandler] cannot auth for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrFailureCountLimitReached)
}
user, err := a.users.GetUserById(c, uid)
if err != nil {
log.Errorf(c, "[authorizations.OAuth2CallbackAuthorizeHandler] failed to get user \"uid:%d\" info, because %s", uid, err.Error())
return nil, errs.ErrUserNotFound
}
if user.Disabled {
log.Warnf(c, "[authorizations.OAuth2CallbackAuthorizeHandler] user \"uid:%d\" is disabled", user.Uid)
return nil, errs.ErrUserIsDisabled
}
if a.CurrentConfig().EnableUserForceVerifyEmail && !user.EmailVerified {
log.Warnf(c, "[authorizations.OAuth2CallbackAuthorizeHandler] user \"uid:%d\" has not verified email", user.Uid)
return nil, errs.ErrEmailIsNotVerified
}
oldTokenClaims := c.GetTokenClaims()
if oldTokenClaims.Type == core.USER_TOKEN_TYPE_OAUTH2_CALLBACK_REQUIRE_VERIFY {
if credential.Password == "" {
return nil, errs.ErrPasswordIsEmpty
}
if !a.users.IsPasswordEqualsUserPassword(credential.Password, user) {
failureCheckErr := a.CheckAndIncreaseFailureCount(c, uid)
if failureCheckErr != nil {
log.Warnf(c, "[authorizations.OAuth2CallbackAuthorizeHandler] cannot login for user \"uid:%d\", because %s", user.Uid, failureCheckErr.Error())
return nil, errs.Or(failureCheckErr, errs.ErrFailureCountLimitReached)
}
return nil, errs.ErrUserPasswordWrong
}
userExternalAuth := &models.UserExternalAuth{
Uid: user.Uid,
ExternalAuthType: userExternalAuthType,
}
if a.CurrentConfig().OAuth2UserIdentifier == settings.OAuth2UserIdentifierEmail {
userExternalAuth.ExternalEmail = user.Email
} else if a.CurrentConfig().OAuth2UserIdentifier == settings.OAuth2UserIdentifierUsername {
userExternalAuth.ExternalUsername = user.Username
}
err = a.userExternalAuths.CreateUserExternalAuth(c, userExternalAuth)
if err != nil {
log.Errorf(c, "[authorizations.OAuth2CallbackAuthorizeHandler] failed to create user external auth for user \"uid:%d\", because %s", user.Uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
log.Infof(c, "[authorizations.OAuth2CallbackAuthorizeHandler] user external auth has been created for user \"uid:%d\"", user.Uid)
} else if oldTokenClaims.Type == core.USER_TOKEN_TYPE_OAUTH2_CALLBACK {
_, err = a.userExternalAuths.GetUserExternalAuthByUid(c, uid, userExternalAuthType)
if err != nil {
log.Errorf(c, "[authorizations.OAuth2CallbackAuthorizeHandler] failed to get user external auth for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrUserExternalAuthNotFound)
}
} else {
return nil, errs.ErrSystemError
}
err = a.tokens.DeleteTokenByClaims(c, oldTokenClaims)
if err != nil {
log.Warnf(c, "[authorizations.OAuth2CallbackAuthorizeHandler] failed to revoke temporary token \"utid:%s\" for user \"uid:%d\", because %s", oldTokenClaims.UserTokenId, user.Uid, err.Error())
}
token, claims, err := a.tokens.CreateToken(c, user)
if err != nil {
log.Errorf(c, "[authorizations.OAuth2CallbackAuthorizeHandler] failed to create token for user \"uid:%d\", because %s", user.Uid, err.Error())
return nil, errs.ErrTokenGenerating
}
c.SetTextualToken(token)
c.SetTokenClaims(claims)
userApplicationCloudSettings, err := a.userAppCloudSettings.GetUserApplicationCloudSettingsByUid(c, user.Uid)
var applicationCloudSettingSlice *models.ApplicationCloudSettingSlice = nil
if err != nil {
log.Warnf(c, "[authorizations.OAuth2CallbackAuthorizeHandler] failed to get latest user application cloud settings for user \"uid:%d\", because %s", user.Uid, err.Error())
} else if userApplicationCloudSettings != nil && len(userApplicationCloudSettings.Settings) > 0 {
applicationCloudSettingSlice = &userApplicationCloudSettings.Settings
}
log.Infof(c, "[authorizations.OAuth2CallbackAuthorizeHandler] user \"uid:%d\" has logged in, token will be expired at %d", user.Uid, claims.ExpiresAt)
authResp := a.getAuthResponse(c, token, false, user, applicationCloudSettingSlice)
return authResp, nil
}
func (a *AuthorizationsApi) getAuthResponse(c *core.WebContext, token string, need2FA bool, user *models.User, applicationCloudSettings *models.ApplicationCloudSettingSlice) *models.AuthResponse {
return &models.AuthResponse{
Token: token,