Skip to content
This repository was archived by the owner on Jun 11, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions apps/auth/internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,14 @@ var Module = fx.Module(

fx.Provide(
func(ev *env.Env) (*recaptchaenterprise.Client, error) {
client, err := recaptchaenterprise.NewClient(context.TODO())
if err != nil {
return nil, err
if ev.GoogleRecaptchaEnabled {
client, err := recaptchaenterprise.NewClient(context.TODO())
if err != nil {
return nil, err
}
return client, nil
}
return client, nil
return nil, nil
},
),

Expand Down
30 changes: 18 additions & 12 deletions apps/auth/internal/domain/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,16 @@ func (d *domainI) verifyCaptcha(ctx context.Context, token string) (bool, error)
}

func (d *domainI) SignUp(ctx context.Context, name string, email string, password string, captchaToken string) (*common.AuthSession, error) {
isValidCaptcha, err := d.verifyCaptcha(ctx, captchaToken)
if err != nil {
return nil, errors.Newf("failed to verify CAPTCHA: %v", err)
}

if !isValidCaptcha {
return nil, errors.New("CAPTCHA verification failed")
if d.envVars.GoogleRecaptchaEnabled {
isValidCaptcha, err := d.verifyCaptcha(ctx, captchaToken)
if err != nil {
return nil, errors.Newf("failed to verify CAPTCHA: %v", err)
}

if !isValidCaptcha {
return nil, errors.New("CAPTCHA verification failed")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Update error messages to be context-aware

Now that CAPTCHA is optional, this error message might be confusing if CAPTCHA isn't being used. Consider updating it to something like 'Security verification failed' or make it conditional based on whether CAPTCHA is enabled.

if captchaEnabled {
    return nil, errors.New("Security verification failed")
} else {
    return nil, errors.New("Authentication failed")
}

}
}

matched, err := d.userRepo.FindOne(ctx, repos.Filter{"email": email})
Expand Down Expand Up @@ -342,13 +345,16 @@ func (d *domainI) ResetPassword(ctx context.Context, token string, password stri
}

func (d *domainI) RequestResetPassword(ctx context.Context, email string, captchaToken string) (bool, error) {
isValidCaptcha, err := d.verifyCaptcha(ctx, captchaToken)
if err != nil {
return false, errors.Newf("failed to verify CAPTCHA: %v", err)
}

if !isValidCaptcha {
return false, errors.New("CAPTCHA verification failed")
if d.envVars.GoogleRecaptchaEnabled {
isValidCaptcha, err := d.verifyCaptcha(ctx, captchaToken)
if err != nil {
return false, errors.Newf("failed to verify CAPTCHA: %v", err)
}

if !isValidCaptcha {
return false, errors.New("CAPTCHA verification failed")
}
}

resetToken := generateId("reset")
Expand Down
126 changes: 64 additions & 62 deletions apps/auth/internal/env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"github.com/kloudlite/api/pkg/errors"
)

type Env struct {
type authEnv struct {
MongoUri string `env:"MONGO_URI" required:"true"`
MongoDbName string `env:"MONGO_DB_NAME" required:"true"`
Port uint16 `env:"PORT" required:"true"`
Expand All @@ -18,88 +18,90 @@ type Env struct {

OAuth2Enabled bool `env:"OAUTH2_ENABLED" required:"true"`

OAuth2GithubEnabled bool `env:"OAUTH2_GITHUB_ENABLED" required:"false"`
GithubClientId string `env:"GITHUB_CLIENT_ID" required:"false"`
GithubClientSecret string `env:"GITHUB_CLIENT_SECRET" required:"false"`
GithubCallbackUrl string `env:"GITHUB_CALLBACK_URL" required:"false"`
GithubAppId string `env:"GITHUB_APP_ID" required:"false"`
GithubAppPKFile string `env:"GITHUB_APP_PK_FILE" required:"false"`
GithubScopes string `env:"GITHUB_SCOPES" required:"false"`
GithubWebhookUrl string `env:"GITHUB_WEBHOOK_URL" required:"false"`

OAuth2GitlabEnabled bool `env:"OAUTH2_GITLAB_ENABLED" required:"false"`
GitlabClientId string `env:"GITLAB_CLIENT_ID" required:"false"`
GitlabClientSecret string `env:"GITLAB_CLIENT_SECRET" required:"false"`
GitlabCallbackUrl string `env:"GITLAB_CALLBACK_URL" required:"false"`
GitlabScopes string `env:"GITLAB_SCOPES" required:"false"`
GitlabWebhookUrl string `env:"GITLAB_WEBHOOK_URL" required:"false"`

OAuth2GoogleEnabled bool `env:"OAUTH2_GOOGLE_ENABLED" required:"false"`
GoogleClientId string `env:"GOOGLE_CLIENT_ID" required:"false"`
GoogleClientSecret string `env:"GOOGLE_CLIENT_SECRET" required:"false"`
GoogleCallbackUrl string `env:"GOOGLE_CALLBACK_URL" required:"false"`
GoogleScopes string `env:"GOOGLE_SCOPES" required:"false"`
OAuth2GithubEnabled bool `env:"OAUTH2_GITHUB_ENABLED" default:"false"`
OAuth2GitlabEnabled bool `env:"OAUTH2_GITLAB_ENABLED" default:"false"`
OAuth2GoogleEnabled bool `env:"OAUTH2_GOOGLE_ENABLED" default:"false"`

CommsService string `env:"COMMS_SERVICE" required:"true"`
NatsURL string `env:"NATS_URL" required:"true"`
SessionKVBucket string `env:"SESSION_KV_BUCKET" required:"true"`
VerifyTokenKVBucket string `env:"VERIFY_TOKEN_KV_BUCKET" required:"true"`
ResetPasswordTokenKVBucket string `env:"RESET_PASSWORD_TOKEN_KV_BUCKET" required:"true"`

GoogleCloudProjectId string `env:"GOOGLE_CLOUD_PROJECT_ID" required:"true"`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 suggestion (security): Consider documenting the implications of optional CAPTCHA

The change from required:"true" to required:"false" for these environment variables aligns with the new optional CAPTCHA behavior. Consider adding a comment explaining the security implications of not setting these variables, and under what circumstances it might be appropriate to leave them unset.

RecaptchaSiteKey string `env:"RECAPTCHA_SITE_KEY" required:"true"`
GoogleApplicationCredentials string `env:"GOOGLE_APPLICATION_CREDENTIALS" required:"true"`
GoogleRecaptchaEnabled bool `env:"GOOGLE_RECAPTCHA_ENABLED" default:"false"`

IsDev bool
}

func (ev *Env) validateEnv() error {
if ev.OAuth2Enabled {
if ev.OAuth2GithubEnabled {
err := errors.Newf("when github oauth2 is enabled, secrets `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GITHUB_CALLBACK_URL`, `GITHUB_APP_ID`, `GITHUB_APP_PK_FILE`, `GITHUB_SCOPES` are required")

if ev.GithubClientId == "" ||
ev.GithubClientSecret == "" ||
ev.GithubCallbackUrl == "" ||
ev.GithubAppId == "" ||
ev.GithubAppPKFile == "" ||
ev.GithubScopes == "" {
return err
}
}
type githubOAuthEnv struct {
GithubClientId string `env:"GITHUB_CLIENT_ID" required:"true"`
GithubClientSecret string `env:"GITHUB_CLIENT_SECRET" required:"true"`
GithubCallbackUrl string `env:"GITHUB_CALLBACK_URL" required:"true"`
GithubAppId string `env:"GITHUB_APP_ID" required:"true"`
GithubAppPKFile string `env:"GITHUB_APP_PK_FILE" required:"true"`
GithubScopes string `env:"GITHUB_SCOPES" required:"true"`
GithubWebhookUrl string `env:"GITHUB_WEBHOOK_URL" required:"true"`
}

if ev.OAuth2GitlabEnabled {
err := errors.Newf("when gitlab oauth2 is enabled, secrets `GITLAB_CLIENT_ID`, `GITLAB_CLIENT_SECRET`, `GITLAB_CALLBACK_URL`, `GITLAB_SCOPES` are required")
type gitlabOAuthEnv struct {
GitlabClientId string `env:"GITLAB_CLIENT_ID" required:"true"`
GitlabClientSecret string `env:"GITLAB_CLIENT_SECRET" required:"true"`
GitlabCallbackUrl string `env:"GITLAB_CALLBACK_URL" required:"true"`
GitlabScopes string `env:"GITLAB_SCOPES" required:"true"`
GitlabWebhookUrl string `env:"GITLAB_WEBHOOK_URL" required:"true"`
}

if ev.GitlabClientId == "" ||
ev.GitlabClientSecret == "" ||
ev.GitlabCallbackUrl == "" ||
ev.GitlabScopes == "" {
return errors.NewE(err)
}
}
type googleOAuthEnv struct {
GoogleClientId string `env:"GOOGLE_CLIENT_ID" required:"true"`
GoogleClientSecret string `env:"GOOGLE_CLIENT_SECRET" required:"true"`
GoogleCallbackUrl string `env:"GOOGLE_CALLBACK_URL" required:"true"`
GoogleScopes string `env:"GOOGLE_SCOPES" required:"true"`
}

if ev.OAuth2GoogleEnabled {
err := errors.Newf("when google oauth2 is enabled, secrets `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_CALLBACK_URL`, `GOOGLE_SCOPES` are required")
type googleRecaptchaEnv struct {
GoogleCloudProjectId string `env:"GOOGLE_CLOUD_PROJECT_ID" required:"true"`
RecaptchaSiteKey string `env:"RECAPTCHA_SITE_KEY" required:"true"`
GoogleApplicationCredentials string `env:"GOOGLE_APPLICATION_CREDENTIALS" required:"true"`
}

if ev.GoogleClientId == "" ||
ev.GoogleClientSecret == "" ||
ev.GoogleCallbackUrl == "" ||
ev.GoogleScopes == "" {
return errors.NewE(err)
}
}
}
return nil
type Env struct {
authEnv
githubOAuthEnv
gitlabOAuthEnv
googleOAuthEnv
googleRecaptchaEnv
}

func LoadEnv() (*Env, error) {
var ev Env
if err := env.Set(&ev); err != nil {

if err := env.Set(&ev.authEnv); err != nil {
return nil, errors.NewE(err)
}
if err := ev.validateEnv(); err != nil {
return nil, errors.NewE(err)

if ev.OAuth2GithubEnabled {
if err := env.Set(&ev.githubOAuthEnv); err != nil {
return nil, errors.NewE(err)
}
}

if ev.OAuth2GitlabEnabled {
if err := env.Set(&ev.gitlabOAuthEnv); err != nil {
return nil, errors.NewE(err)
}
}

if ev.OAuth2GoogleEnabled {
if err := env.Set(&ev.googleOAuthEnv); err != nil {
return nil, errors.NewE(err)
}
}

if ev.GoogleRecaptchaEnabled {
if err := env.Set(&ev.googleRecaptchaEnv); err != nil {
return nil, errors.NewE(err)
}
}

return &ev, nil
}