mirror of
https://github.com/pocketbase/pocketbase.git
synced 2025-03-19 14:17:48 +02:00
171 lines
4.5 KiB
Go
171 lines
4.5 KiB
Go
package apis
|
|
|
|
import (
|
|
"log/slog"
|
|
"net/http"
|
|
"slices"
|
|
|
|
"github.com/pocketbase/pocketbase/core"
|
|
"github.com/pocketbase/pocketbase/tools/auth"
|
|
"github.com/pocketbase/pocketbase/tools/security"
|
|
"golang.org/x/oauth2"
|
|
)
|
|
|
|
type otpResponse struct {
|
|
Enabled bool `json:"enabled"`
|
|
Duration int64 `json:"duration"` // in seconds
|
|
}
|
|
|
|
type mfaResponse struct {
|
|
Enabled bool `json:"enabled"`
|
|
Duration int64 `json:"duration"` // in seconds
|
|
}
|
|
|
|
type passwordResponse struct {
|
|
IdentityFields []string `json:"identityFields"`
|
|
Enabled bool `json:"enabled"`
|
|
}
|
|
|
|
type oauth2Response struct {
|
|
Providers []providerInfo `json:"providers"`
|
|
Enabled bool `json:"enabled"`
|
|
}
|
|
|
|
type providerInfo struct {
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"displayName"`
|
|
State string `json:"state"`
|
|
AuthURL string `json:"authURL"`
|
|
|
|
// @todo
|
|
// deprecated: use AuthURL instead
|
|
// AuthUrl will be removed after dropping v0.22 support
|
|
AuthUrl string `json:"authUrl"`
|
|
|
|
// technically could be omitted if the provider doesn't support PKCE,
|
|
// but to avoid breaking existing typed clients we'll return them as empty string
|
|
CodeVerifier string `json:"codeVerifier"`
|
|
CodeChallenge string `json:"codeChallenge"`
|
|
CodeChallengeMethod string `json:"codeChallengeMethod"`
|
|
}
|
|
|
|
type authMethodsResponse struct {
|
|
Password passwordResponse `json:"password"`
|
|
OAuth2 oauth2Response `json:"oauth2"`
|
|
MFA mfaResponse `json:"mfa"`
|
|
OTP otpResponse `json:"otp"`
|
|
|
|
// legacy fields
|
|
// @todo remove after dropping v0.22 support
|
|
AuthProviders []providerInfo `json:"authProviders"`
|
|
UsernamePassword bool `json:"usernamePassword"`
|
|
EmailPassword bool `json:"emailPassword"`
|
|
}
|
|
|
|
func (amr *authMethodsResponse) fillLegacyFields() {
|
|
amr.EmailPassword = amr.Password.Enabled && slices.Contains(amr.Password.IdentityFields, "email")
|
|
|
|
amr.UsernamePassword = amr.Password.Enabled && slices.Contains(amr.Password.IdentityFields, "username")
|
|
|
|
if amr.OAuth2.Enabled {
|
|
amr.AuthProviders = amr.OAuth2.Providers
|
|
}
|
|
}
|
|
|
|
func recordAuthMethods(e *core.RequestEvent) error {
|
|
collection, err := findAuthCollection(e)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
result := authMethodsResponse{
|
|
Password: passwordResponse{
|
|
IdentityFields: make([]string, 0, len(collection.PasswordAuth.IdentityFields)),
|
|
},
|
|
OAuth2: oauth2Response{
|
|
Providers: make([]providerInfo, 0, len(collection.OAuth2.Providers)),
|
|
},
|
|
OTP: otpResponse{
|
|
Enabled: collection.OTP.Enabled,
|
|
},
|
|
MFA: mfaResponse{
|
|
Enabled: collection.MFA.Enabled,
|
|
},
|
|
}
|
|
|
|
if collection.PasswordAuth.Enabled {
|
|
result.Password.Enabled = true
|
|
result.Password.IdentityFields = collection.PasswordAuth.IdentityFields
|
|
}
|
|
|
|
if collection.OTP.Enabled {
|
|
result.OTP.Duration = collection.OTP.Duration
|
|
}
|
|
|
|
if collection.MFA.Enabled {
|
|
result.MFA.Duration = collection.MFA.Duration
|
|
}
|
|
|
|
if !collection.OAuth2.Enabled {
|
|
result.fillLegacyFields()
|
|
|
|
return e.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
result.OAuth2.Enabled = true
|
|
|
|
for _, config := range collection.OAuth2.Providers {
|
|
provider, err := config.InitProvider()
|
|
if err != nil {
|
|
e.App.Logger().Debug(
|
|
"Failed to setup OAuth2 provider",
|
|
slog.String("name", config.Name),
|
|
slog.String("error", err.Error()),
|
|
)
|
|
continue // skip provider
|
|
}
|
|
|
|
info := providerInfo{
|
|
Name: config.Name,
|
|
DisplayName: provider.DisplayName(),
|
|
State: security.RandomString(30),
|
|
}
|
|
|
|
if info.DisplayName == "" {
|
|
info.DisplayName = config.Name
|
|
}
|
|
|
|
urlOpts := []oauth2.AuthCodeOption{}
|
|
|
|
// custom providers url options
|
|
switch config.Name {
|
|
case auth.NameApple:
|
|
// see https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/incorporating_sign_in_with_apple_into_other_platforms#3332113
|
|
urlOpts = append(urlOpts, oauth2.SetAuthURLParam("response_mode", "form_post"))
|
|
}
|
|
|
|
if provider.PKCE() {
|
|
info.CodeVerifier = security.RandomString(43)
|
|
info.CodeChallenge = security.S256Challenge(info.CodeVerifier)
|
|
info.CodeChallengeMethod = "S256"
|
|
urlOpts = append(urlOpts,
|
|
oauth2.SetAuthURLParam("code_challenge", info.CodeChallenge),
|
|
oauth2.SetAuthURLParam("code_challenge_method", info.CodeChallengeMethod),
|
|
)
|
|
}
|
|
|
|
info.AuthURL = provider.BuildAuthURL(
|
|
info.State,
|
|
urlOpts...,
|
|
) + "&redirect_uri=" // empty redirect_uri so that users can append their redirect url
|
|
|
|
info.AuthUrl = info.AuthURL
|
|
|
|
result.OAuth2.Providers = append(result.OAuth2.Providers, info)
|
|
}
|
|
|
|
result.fillLegacyFields()
|
|
|
|
return e.JSON(http.StatusOK, result)
|
|
}
|