diff --git a/.secrets.baseline b/.secrets.baseline index a29325fc0..7fea98b55 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -185,21 +185,21 @@ "filename": "core/auth/auth.go", "hashed_secret": "58f3388441fbce0e48aef2bf74413a6f43f6dc70", "is_verified": false, - "line_number": 991 + "line_number": 992 }, { "type": "Secret Keyword", "filename": "core/auth/auth.go", "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", "is_verified": false, - "line_number": 2676 + "line_number": 2682 }, { "type": "Secret Keyword", "filename": "core/auth/auth.go", "hashed_secret": "94a7f0195bbbd2260c4e4d02b6348fbcd90b2b30", "is_verified": false, - "line_number": 2806 + "line_number": 2812 } ], "core/auth/auth_type_oidc.go": [ @@ -233,7 +233,7 @@ "filename": "core/auth/identifier_type_email.go", "hashed_secret": "69411040443be576ce64fc793269d7c26dd0866a", "is_verified": false, - "line_number": 251 + "line_number": 249 } ], "core/auth/service_static_token.go": [ @@ -377,5 +377,5 @@ } ] }, - "generated_at": "2023-10-06T23:08:57Z" + "generated_at": "2023-10-11T21:26:54Z" } diff --git a/core/auth/apis.go b/core/auth/apis.go index 3ee2b99db..780df723c 100644 --- a/core/auth/apis.go +++ b/core/auth/apis.go @@ -1094,7 +1094,7 @@ func (a *Auth) UpdateCredential(accountID string, accountAuthTypeID string, para //Update the credential with new password accountAuthType.Credential.Value = authTypeCreds - if err = a.storage.UpdateCredentialValue(accountAuthType.Credential.ID, accountAuthType.Credential.Value); err != nil { + if err = a.storage.UpdateCredentialValue(nil, accountAuthType.Credential.ID, accountAuthType.Credential.Value); err != nil { return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeCredential, nil, err) } @@ -1147,7 +1147,7 @@ func (a *Auth) ResetForgotCredential(credsID string, resetCode string, params st } //Update the credential with new password credential.Value = authTypeCreds - if err = a.storage.UpdateCredentialValue(credential.ID, credential.Value); err != nil { + if err = a.storage.UpdateCredentialValue(nil, credential.ID, credential.Value); err != nil { return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeCredential, nil, err) } @@ -1243,7 +1243,7 @@ func (a *Auth) ForgotCredential(authenticationType string, identifierJSON string //Update the credential with reset code and expiry credential.Value = authTypeCreds - if err = a.storage.UpdateCredentialValue(credential.ID, credential.Value); err != nil { + if err = a.storage.UpdateCredentialValue(nil, credential.ID, credential.Value); err != nil { return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeCredential, nil, err) } return nil diff --git a/core/auth/auth.go b/core/auth/auth.go index c605390c5..6ce3ae2ce 100644 --- a/core/auth/auth.go +++ b/core/auth/auth.go @@ -625,6 +625,7 @@ func (a *Auth) applyAuthType(supportedAuthType model.SupportedAuthType, appOrg m var account *model.Account if identifierImpl == nil { + // if given an account identifier ID, find the account and attempt sign in if accountIdentifierID != nil { account, err = a.storage.FindAccountByIdentifierID(nil, *accountIdentifierID) if err != nil { @@ -2608,6 +2609,11 @@ func (a *Auth) updateExternalIdentifiers(account *model.Account, accountAuthType primary := (externalUser.Email == externalUser.Identifier) account.Identifiers[i].Identifier = externalUser.Email account.Identifiers[i].Primary = &primary + // if the external email is not already verified, set verified to the default setting + if !account.Identifiers[i].Verified { + account.Identifiers[i].Verified = externalUser.IsEmailVerified + } + updated = true } if hasExternalEmail { diff --git a/core/auth/auth_type_code.go b/core/auth/auth_type_code.go index 0977f8726..411985634 100644 --- a/core/auth/auth_type_code.go +++ b/core/auth/auth_type_code.go @@ -99,14 +99,14 @@ func (a *codeAuthImpl) checkCredentials(identifierImpl identifierType, accountID if identifierChannel.requiresCodeGeneration() { if incomingCode == "" { // generate a new code - code := strconv.Itoa(utils.GenerateRandomInt(1000000)) - padLen := 6 - len(code) + incomingCode = strconv.Itoa(utils.GenerateRandomInt(1000000)) + padLen := 6 - len(incomingCode) if padLen > 0 { - code = strings.Repeat("0", padLen) + code + incomingCode = strings.Repeat("0", padLen) + incomingCode } // store generated codes in login state collection - state := map[string]interface{}{stateKeyCode: code} + state := map[string]interface{}{stateKeyCode: incomingCode} loginState := model.LoginState{ID: uuid.NewString(), AppID: appOrg.Application.ID, OrgID: appOrg.Organization.ID, AccountID: accountID, State: state, DateCreated: time.Now().UTC()} err := a.auth.storage.InsertLoginState(loginState) if err != nil { @@ -125,6 +125,11 @@ func (a *codeAuthImpl) checkCredentials(identifierImpl identifierType, accountID return "", "", errors.ErrorData(logutils.StatusInvalid, "code", logutils.StringArgs(*incomingCreds.Code)) } + err = a.auth.storage.DeleteLoginState(nil, loginState.ID) + if err != nil { + return "", "", errors.WrapErrorAction(logutils.ActionDelete, model.TypeLoginState, nil, err) + } + return "", "", nil } } diff --git a/core/auth/auth_type_webauthn.go b/core/auth/auth_type_webauthn.go index 9234cf38e..18d628545 100644 --- a/core/auth/auth_type_webauthn.go +++ b/core/auth/auth_type_webauthn.go @@ -431,6 +431,12 @@ func (a *webAuthnAuthImpl) completeRegistration(response *protocol.ParsedCredent return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeAccountAuthType, &logutils.FieldArgs{"id": accountAuthType.ID, "account_id": accountIDVal}, err) } + //3. remove the login state + err = a.auth.storage.DeleteLoginState(context, loginState.ID) + if err != nil { + return errors.WrapErrorAction(logutils.ActionDelete, model.TypeLoginState, nil, err) + } + return nil } @@ -596,9 +602,25 @@ func (a *webAuthnAuthImpl) completeLogin(response *protocol.ParsedCredentialAsse } credential.Value[credentialKeyCredential] = string(credentialData) - err = a.auth.storage.UpdateCredentialValue(credID, credential.Value) + transaction := func(context storage.TransactionContext) error { + //1. update credential + err = a.auth.storage.UpdateCredentialValue(context, credID, credential.Value) + if err != nil { + return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeCredential, nil, err) + } + + //2. remove the login state + err = a.auth.storage.DeleteLoginState(context, loginState.ID) + if err != nil { + return errors.WrapErrorAction(logutils.ActionDelete, model.TypeLoginState, nil, err) + } + + return nil + } + + err = a.auth.storage.PerformTransaction(transaction) if err != nil { - return "", errors.WrapErrorAction(logutils.ActionUpdate, model.TypeCredential, nil, err) + return "", err } } diff --git a/core/auth/identifier_type_email.go b/core/auth/identifier_type_email.go index 02c090d08..99f67edea 100644 --- a/core/auth/identifier_type_email.go +++ b/core/auth/identifier_type_email.go @@ -21,7 +21,6 @@ import ( "encoding/json" "fmt" "net/url" - "regexp" "strings" "time" @@ -72,8 +71,7 @@ func (a *emailIdentifierImpl) withIdentifier(creds string) (identifierType, erro } email := strings.TrimSpace(requestCreds.Email) - validEmail := regexp.MustCompile(`^[a-zA-Z0-9.!#\$%&'*+/=?^_{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$`) - if !validEmail.MatchString(email) { + if !utils.IsValidEmail(email) { return nil, errors.ErrorData(logutils.StatusInvalid, typeEmailIdentifier, &logutils.FieldArgs{"email": email}) } diff --git a/core/auth/identifier_type_phone.go b/core/auth/identifier_type_phone.go index 9eca87e40..2452542f7 100644 --- a/core/auth/identifier_type_phone.go +++ b/core/auth/identifier_type_phone.go @@ -19,7 +19,6 @@ import ( "core-building-block/utils" "encoding/json" "net/url" - "regexp" "time" "github.com/google/uuid" @@ -68,8 +67,7 @@ func (a *phoneIdentifierImpl) withIdentifier(creds string) (identifierType, erro return nil, errors.WrapErrorAction(logutils.ActionValidate, typePhoneIdentifier, nil, err) } - validPhone := regexp.MustCompile(`^\+[1-9]\d{1,14}$`) - if !validPhone.MatchString(requestCreds.Phone) { + if !utils.IsValidPhone(requestCreds.Phone) { return nil, errors.ErrorData(logutils.StatusInvalid, typePhoneNumber, &logutils.FieldArgs{"phone": requestCreds.Phone}) } diff --git a/core/auth/interfaces.go b/core/auth/interfaces.go index 297d939b1..47bf61b4e 100644 --- a/core/auth/interfaces.go +++ b/core/auth/interfaces.go @@ -554,6 +554,7 @@ type Storage interface { //LoginStates FindLoginState(appID string, orgID string, accountID *string, stateParams map[string]interface{}) (*model.LoginState, error) InsertLoginState(loginState model.LoginState) error + DeleteLoginState(context storage.TransactionContext, id string) error //Accounts FindAccount(context storage.TransactionContext, appOrgID string, code string, identifier string) (*model.Account, error) @@ -606,7 +607,7 @@ type Storage interface { FindCredential(context storage.TransactionContext, ID string) (*model.Credential, error) FindCredentials(context storage.TransactionContext, ids []string) ([]model.Credential, error) UpdateCredential(context storage.TransactionContext, creds *model.Credential) error - UpdateCredentialValue(ID string, value map[string]interface{}) error + UpdateCredentialValue(context storage.TransactionContext, ID string, value map[string]interface{}) error DeleteCredential(context storage.TransactionContext, ID string) error //MFA diff --git a/driven/storage/adapter.go b/driven/storage/adapter.go index b8e1f88dc..15893f2e4 100644 --- a/driven/storage/adapter.go +++ b/driven/storage/adapter.go @@ -612,6 +612,21 @@ func (sa *Adapter) InsertLoginState(loginState model.LoginState) error { return nil } +// DeleteLoginState inserts a new login state +func (sa *Adapter) DeleteLoginState(context TransactionContext, id string) error { + filter := bson.M{"_id": id} + + res, err := sa.db.loginStates.DeleteOneWithContext(context, filter, nil) + if err != nil { + return errors.WrapErrorAction(logutils.ActionDelete, model.TypeLoginState, &logutils.FieldArgs{"_id": id}, err) + } + if res.DeletedCount > 1 { + return errors.ErrorAction(logutils.ActionDelete, model.TypeLoginState, &logutils.FieldArgs{"_id": id, "deleted": res.DeletedCount, "expected": 1}) + } + + return nil +} + // FindAccount finds an account for app, org, auth type and identifier func (sa *Adapter) FindAccount(context TransactionContext, appOrgID string, code string, identifier string) (*model.Account, error) { filter := bson.M{"app_org_id": appOrgID, "identifiers": bson.M{ @@ -1961,7 +1976,7 @@ func (sa *Adapter) UpdateCredential(context TransactionContext, creds *model.Cre } // UpdateCredentialValue updates the value in credentials collection -func (sa *Adapter) UpdateCredentialValue(ID string, value map[string]interface{}) error { +func (sa *Adapter) UpdateCredentialValue(context TransactionContext, ID string, value map[string]interface{}) error { filter := bson.D{primitive.E{Key: "_id", Value: ID}} update := bson.D{ primitive.E{Key: "$set", Value: bson.D{ @@ -1970,7 +1985,7 @@ func (sa *Adapter) UpdateCredentialValue(ID string, value map[string]interface{} }}, } - res, err := sa.db.credentials.UpdateOne(filter, update, nil) + res, err := sa.db.credentials.UpdateOneWithContext(context, filter, update, nil) if err != nil { return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeCredential, nil, err) } diff --git a/driven/storage/migrations.go b/driven/storage/migrations.go index 540e7bf94..e7c11cba8 100644 --- a/driven/storage/migrations.go +++ b/driven/storage/migrations.go @@ -373,9 +373,14 @@ func (sa *Adapter) migrateAccounts(context TransactionContext, appOrg model.Appl authTypes = append(authTypes, newAat) } + // if there are no valid auth types then the account is inaccessible, so do not re-insert it + if len(authTypes) == 0 { + continue + } + now := time.Now().UTC() - // add profile email to identifiers if not already there - if acct.Profile.Email != nil && *acct.Profile.Email != "" { + // add profile email to identifiers if valid and not already there + if acct.Profile.Email != nil && utils.IsValidEmail(*acct.Profile.Email) { foundEmail := false for _, identifier := range identifiers { if identifier.Code == "email" && identifier.Identifier == *acct.Profile.Email { @@ -384,12 +389,11 @@ func (sa *Adapter) migrateAccounts(context TransactionContext, appOrg model.Appl } } if !foundEmail { - emailIdentifier := accountIdentifier{ID: uuid.NewString(), Code: "email", Identifier: *acct.Profile.Email, Sensitive: true, DateCreated: now} - identifiers = append(identifiers, emailIdentifier) + identifiers = append(identifiers, accountIdentifier{ID: uuid.NewString(), Code: "email", Identifier: *acct.Profile.Email, Sensitive: true, DateCreated: now}) } } - // add profile phone to identifiers if not already there - if acct.Profile.Phone != nil && *acct.Profile.Phone != "" { + // add profile phone to identifiers if valid and not already there + if acct.Profile.Phone != nil && utils.IsValidPhone(*acct.Profile.Phone) { foundPhone := false for _, identifier := range identifiers { if identifier.Code == "phone" && identifier.Identifier == *acct.Profile.Phone { diff --git a/utils/utils.go b/utils/utils.go index fc329effe..c6be11cfc 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -25,6 +25,7 @@ import ( "math/rand" "net/http" "reflect" + "regexp" "strings" "time" @@ -181,6 +182,18 @@ func GetLogValue(value string, n int) string { return fmt.Sprintf("***%s", lastN) } +// IsValidPhone reports whether phone is a valid phone number +func IsValidPhone(phone string) bool { + validPhone := regexp.MustCompile(`^\+[1-9]\d{1,14}$`) + return validPhone.MatchString(phone) +} + +// IsValidEmail reports whether email is a valid email address +func IsValidEmail(email string) bool { + validEmail := regexp.MustCompile(`^[a-zA-Z0-9.!#\$%&'*+/=?^_{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$`) + return validEmail.MatchString(email) +} + // FormatTime formats the time value which this pointer points. Gives empty string if the pointer is nil func FormatTime(v *time.Time) string { if v == nil {