package forms import ( "errors" "fmt" validation "github.com/go-ozzo/ozzo-validation/v4" "github.com/go-ozzo/ozzo-validation/v4/is" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/tools/auth" "github.com/pocketbase/pocketbase/tools/security" "golang.org/x/oauth2" ) // UserOauth2Login defines a user Oauth2 login form. type UserOauth2Login struct { app core.App // The name of the OAuth2 client provider (eg. "google") Provider string `form:"provider" json:"provider"` // The authorization code returned from the initial request. Code string `form:"code" json:"code"` // The code verifier sent with the initial request as part of the code_challenge. CodeVerifier string `form:"codeVerifier" json:"codeVerifier"` // The redirect url sent with the initial request. RedirectUrl string `form:"redirectUrl" json:"redirectUrl"` } // NewUserOauth2Login creates a new user Oauth2 login form. func NewUserOauth2Login(app core.App) *UserOauth2Login { return &UserOauth2Login{app: app} } // Validate makes the form validatable by implementing [validation.Validatable] interface. func (form *UserOauth2Login) Validate() error { return validation.ValidateStruct(form, validation.Field(&form.Provider, validation.Required, validation.By(form.checkProviderName)), validation.Field(&form.Code, validation.Required), validation.Field(&form.CodeVerifier, validation.Required), validation.Field(&form.RedirectUrl, validation.Required, is.URL), ) } func (form *UserOauth2Login) checkProviderName(value any) error { name, _ := value.(string) config, ok := form.app.Settings().NamedAuthProviderConfigs()[name] if !ok || !config.Enabled { return validation.NewError("validation_invalid_provider", fmt.Sprintf("%q is missing or is not enabled.", name)) } return nil } // Submit validates and submits the form. // On success returns the authorized user model and the fetched provider's data. func (form *UserOauth2Login) Submit() (*models.User, *auth.AuthUser, error) { if err := form.Validate(); err != nil { return nil, nil, err } provider, err := auth.NewProviderByName(form.Provider) if err != nil { return nil, nil, err } config := form.app.Settings().NamedAuthProviderConfigs()[form.Provider] config.SetupProvider(provider) provider.SetRedirectUrl(form.RedirectUrl) // fetch token token, err := provider.FetchToken( form.Code, oauth2.SetAuthURLParam("code_verifier", form.CodeVerifier), ) if err != nil { return nil, nil, err } // fetch auth user authData, err := provider.FetchAuthUser(token) if err != nil { return nil, nil, err } // login/register the auth user user, _ := form.app.Dao().FindUserByEmail(authData.Email) if user != nil { // update the existing user's verified state if !user.Verified { user.Verified = true if err := form.app.Dao().SaveUser(user); err != nil { return nil, authData, err } } return user, authData, nil } if !config.AllowRegistrations { // registration of new users is not allowed via the Oauth2 provider return nil, authData, errors.New("Cannot find user with the authorized email.") } // create new user user = &models.User{Verified: true} upsertForm := NewUserUpsert(form.app, user) upsertForm.Email = authData.Email upsertForm.Password = security.RandomString(30) upsertForm.PasswordConfirm = upsertForm.Password event := &core.UserOauth2RegisterEvent{ User: user, AuthData: authData, } if err := form.app.OnUserBeforeOauth2Register().Trigger(event); err != nil { return nil, authData, err } if err := upsertForm.Submit(); err != nil { return nil, authData, err } if err := form.app.OnUserAfterOauth2Register().Trigger(event); err != nil { return nil, authData, err } return user, authData, nil }