diff --git a/.secrets.baseline b/.secrets.baseline
index 4990d5592..61a9f6f5c 100644
--- a/.secrets.baseline
+++ b/.secrets.baseline
@@ -75,6 +75,10 @@
{
"path": "detect_secrets.filters.allowlist.is_line_allowlisted"
},
+ {
+ "path": "detect_secrets.filters.common.is_baseline_file",
+ "filename": ".secrets.baseline"
+ },
{
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
"min_level": 2
@@ -117,103 +121,68 @@
"line_number": 37
}
],
+ "core/apis.go": [
+ {
+ "type": "Secret Keyword",
+ "filename": "core/apis.go",
+ "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c",
+ "is_verified": false,
+ "line_number": 95
+ }
+ ],
"core/app_shared.go": [
{
"type": "Secret Keyword",
"filename": "core/app_shared.go",
"hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c",
"is_verified": false,
- "line_number": 41
+ "line_number": 42
}
],
"core/auth/apis.go": [
+ {
+ "type": "Secret Keyword",
+ "filename": "core/auth/apis.go",
+ "hashed_secret": "04e110541a2e8b44bc10939bfaf5d82adfe45158",
+ "is_verified": false,
+ "line_number": 1944
+ },
{
"type": "Secret Keyword",
"filename": "core/auth/apis.go",
"hashed_secret": "394e3412459f79523e12e1fa95a4cf141ccff122",
"is_verified": false,
- "line_number": 2100
+ "line_number": 2277
}
],
"core/auth/auth.go": [
{
"type": "Secret Keyword",
"filename": "core/auth/auth.go",
- "hashed_secret": "417355fe2b66baa6826739a6d8006ab2ddcf5186",
+ "hashed_secret": "3fea7ef2cdd6ecf5280c66dbd062272664559d83",
"is_verified": false,
- "line_number": 151
+ "line_number": 160
},
{
"type": "Secret Keyword",
"filename": "core/auth/auth.go",
- "hashed_secret": "a358987289cd70bbf50fb10acbcb9bff73c66df6",
+ "hashed_secret": "4a0043e461375664a5656fbdda0d3c39a42a1af4",
"is_verified": false,
- "line_number": 153
+ "line_number": 162
},
{
"type": "Secret Keyword",
"filename": "core/auth/auth.go",
"hashed_secret": "58f3388441fbce0e48aef2bf74413a6f43f6dc70",
"is_verified": false,
- "line_number": 937
+ "line_number": 982
},
{
"type": "Secret Keyword",
"filename": "core/auth/auth.go",
"hashed_secret": "94a7f0195bbbd2260c4e4d02b6348fbcd90b2b30",
"is_verified": false,
- "line_number": 2441
- }
- ],
- "core/auth/auth_type_email.go": [
- {
- "type": "Secret Keyword",
- "filename": "core/auth/auth_type_email.go",
- "hashed_secret": "f3f2fb17a3bf9f307cb6e79b61b9d4baf07dd681",
- "is_verified": false,
- "line_number": 75
- },
- {
- "type": "Secret Keyword",
- "filename": "core/auth/auth_type_email.go",
- "hashed_secret": "fe70d8c51780596c0b3399573122bba943a461da",
- "is_verified": false,
- "line_number": 76
- },
- {
- "type": "Secret Keyword",
- "filename": "core/auth/auth_type_email.go",
- "hashed_secret": "06354d205ab5a3b6c7ad2333c58f1ddc810c97ba",
- "is_verified": false,
- "line_number": 87
- },
- {
- "type": "Secret Keyword",
- "filename": "core/auth/auth_type_email.go",
- "hashed_secret": "7cbe6dcf7274355d223e3174e4d8a7ffb55a9227",
- "is_verified": false,
- "line_number": 156
- },
- {
- "type": "Secret Keyword",
- "filename": "core/auth/auth_type_email.go",
- "hashed_secret": "69411040443be576ce64fc793269d7c26dd0866a",
- "is_verified": false,
- "line_number": 253
- },
- {
- "type": "Secret Keyword",
- "filename": "core/auth/auth_type_email.go",
- "hashed_secret": "cba104f0870345d3ec99d55c06441bdce9fcf584",
- "is_verified": false,
- "line_number": 390
- },
- {
- "type": "Secret Keyword",
- "filename": "core/auth/auth_type_email.go",
- "hashed_secret": "c74f3640d83fd19d941a4f44b28fbd9e57f59eef",
- "is_verified": false,
- "line_number": 391
+ "line_number": 2730
}
],
"core/auth/auth_type_oidc.go": [
@@ -222,51 +191,32 @@
"filename": "core/auth/auth_type_oidc.go",
"hashed_secret": "0ade4f3edccc8888bef404fe6b3c92c13cdfad6b",
"is_verified": false,
- "line_number": 376
+ "line_number": 400
}
],
- "core/auth/auth_type_username.go": [
- {
- "type": "Secret Keyword",
- "filename": "core/auth/auth_type_username.go",
- "hashed_secret": "86f4f81d8dcd41f5f695464a3bba658467957bb3",
- "is_verified": false,
- "line_number": 64
- },
- {
- "type": "Secret Keyword",
- "filename": "core/auth/auth_type_username.go",
- "hashed_secret": "d6f3638bf6ffed24773951f1a48460efa6766362",
- "is_verified": false,
- "line_number": 65
- },
- {
- "type": "Secret Keyword",
- "filename": "core/auth/auth_type_username.go",
- "hashed_secret": "06354d205ab5a3b6c7ad2333c58f1ddc810c97ba",
- "is_verified": false,
- "line_number": 77
- },
+ "core/auth/auth_type_password.go": [
{
"type": "Secret Keyword",
- "filename": "core/auth/auth_type_username.go",
- "hashed_secret": "7cbe6dcf7274355d223e3174e4d8a7ffb55a9227",
+ "filename": "core/auth/auth_type_password.go",
+ "hashed_secret": "ed4434126edb03dc832260a730ccf3bb61af1396",
"is_verified": false,
- "line_number": 179
+ "line_number": 91
},
{
"type": "Secret Keyword",
- "filename": "core/auth/auth_type_username.go",
- "hashed_secret": "cba104f0870345d3ec99d55c06441bdce9fcf584",
+ "filename": "core/auth/auth_type_password.go",
+ "hashed_secret": "8a1618d670f9d2d7d0b26c1d80227ead407f66dd",
"is_verified": false,
- "line_number": 215
- },
+ "line_number": 197
+ }
+ ],
+ "core/auth/identifier_type_email.go": [
{
"type": "Secret Keyword",
- "filename": "core/auth/auth_type_username.go",
- "hashed_secret": "c74f3640d83fd19d941a4f44b28fbd9e57f59eef",
+ "filename": "core/auth/identifier_type_email.go",
+ "hashed_secret": "69411040443be576ce64fc793269d7c26dd0866a",
"is_verified": false,
- "line_number": 216
+ "line_number": 251
}
],
"core/auth/service_static_token.go": [
@@ -275,7 +225,7 @@
"filename": "core/auth/service_static_token.go",
"hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c",
"is_verified": false,
- "line_number": 78
+ "line_number": 71
}
],
"driven/emailer/adapter.go": [
@@ -293,7 +243,7 @@
"filename": "driven/profilebb/adapter.go",
"hashed_secret": "36c48d6ac9d10902792fa78b9c2d7d535971c2cc",
"is_verified": false,
- "line_number": 224
+ "line_number": 223
}
],
"driven/storage/database.go": [
@@ -302,7 +252,23 @@
"filename": "driven/storage/database.go",
"hashed_secret": "6547f385c6d867e20f8217018a4d468a7d67d638",
"is_verified": false,
- "line_number": 216
+ "line_number": 224
+ }
+ ],
+ "driven/storage/migrations.go": [
+ {
+ "type": "Secret Keyword",
+ "filename": "driven/storage/migrations.go",
+ "hashed_secret": "fd9a601da67dbaa273e7fb64877518ee9e408057",
+ "is_verified": false,
+ "line_number": 141
+ },
+ {
+ "type": "Secret Keyword",
+ "filename": "driven/storage/migrations.go",
+ "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c",
+ "is_verified": false,
+ "line_number": 156
}
],
"driver/web/apis_system.go": [
@@ -329,7 +295,7 @@
"filename": "driver/web/docs/gen/gen_types.go",
"hashed_secret": "c9739eab2dfa093cc0e450bf0ea81a43ae67b581",
"is_verified": false,
- "line_number": 1797
+ "line_number": 1920
}
],
"driver/web/docs/resources/admin/auth/login.yaml": [
@@ -347,7 +313,7 @@
"filename": "driver/web/docs/resources/services/auth/account/auth-type/link.yaml",
"hashed_secret": "448ed7416fce2cb66c285d182b1ba3df1e90016d",
"is_verified": false,
- "line_number": 26
+ "line_number": 23
}
],
"driver/web/docs/resources/services/auth/login.yaml": [
@@ -360,5 +326,5 @@
}
]
},
- "generated_at": "2023-10-06T19:34:36Z"
+ "generated_at": "2023-10-03T21:38:39Z"
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0f49b5e3c..1122836fe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,13 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
-
### Added
+- WebAuthn authentication [#659](https://github.com/rokwire/core-building-block/issues/659)
- Searching follows looks for substring matches [#670](https://github.com/rokwire/core-building-block/issues/670)
-
-### Added
- Support following accounts [#667](https://github.com/rokwire/core-building-block/issues/667)
- Device ID not nullable [#672](https://github.com/rokwire/core-building-block/issues/672)
+### Changed
+- Decouple authentication and verification mechanisms [#665](https://github.com/rokwire/core-building-block/issues/665)
+- Refactor account auth types [#674](https://github.com/rokwire/core-building-block/issues/674)
## [1.34.0] - 2023-07-06
### Added
diff --git a/core/apis.go b/core/apis.go
index b4f43b3fa..54591a1d0 100644
--- a/core/apis.go
+++ b/core/apis.go
@@ -22,6 +22,7 @@ import (
"time"
"github.com/google/uuid"
+ "github.com/rokwire/core-auth-library-go/v3/authutils"
"github.com/rokwire/core-auth-library-go/v3/tokenauth"
"github.com/rokwire/logging-library-go/v2/errors"
"github.com/rokwire/logging-library-go/v2/logs"
@@ -47,7 +48,9 @@ type APIs struct {
systemAccountEmail string
systemAccountPassword string
- verifyEmail bool
+ verifyEmail bool
+ verifyWaitTime int
+ verifyExpiry int
logger *logs.Logger
}
@@ -84,23 +87,56 @@ func (c *APIs) storeSystemData() error {
transaction := func(context storage.TransactionContext) error {
createAccount := false
- //1. insert email auth type if does not exist
- emailAuthType, err := c.app.storage.FindAuthType(auth.AuthTypeEmail)
+ //1. insert password auth type if does not exist
+ passwordAuthType, err := c.app.storage.FindAuthType(auth.AuthTypePassword)
if err != nil {
return errors.WrapErrorAction(logutils.ActionFind, model.TypeAuthType, nil, err)
}
- if emailAuthType == nil {
+ if passwordAuthType == nil {
newDocuments["auth_type"] = uuid.NewString()
- params := map[string]interface{}{"verify_email": c.verifyEmail}
- emailAuthType = &model.AuthType{ID: newDocuments["auth_type"], Code: auth.AuthTypeEmail, Description: "Authentication type relying on email and password",
- IsExternal: false, IsAnonymous: false, UseCredentials: true, IgnoreMFA: false, Params: params}
- _, err = c.app.storage.InsertAuthType(context, *emailAuthType)
+ passwordAuthType = &model.AuthType{ID: newDocuments["auth_type"], Code: auth.AuthTypePassword, Description: "Authentication type relying on password",
+ IsExternal: false, IsAnonymous: false, UseCredentials: true, IgnoreMFA: false, Aliases: []string{auth.IdentifierTypeEmail, auth.IdentifierTypeUsername}}
+ _, err = c.app.storage.InsertAuthType(context, *passwordAuthType)
if err != nil {
return errors.WrapErrorAction(logutils.ActionInsert, model.TypeAuthType, nil, err)
}
}
- //2. insert system org if does not exist
+ //2. update auth config or insert if it does not exist
+ config, err := c.app.storage.FindConfig(model.ConfigTypeAuth, authutils.AllApps, authutils.AllOrgs)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionFind, model.TypeConfig, &logutils.FieldArgs{"type": model.ConfigTypeAuth, "app_id": authutils.AllApps, "org_id": authutils.AllOrgs}, err)
+ }
+ if config == nil {
+ configData := model.AuthConfigData{EmailShouldVerify: &c.verifyEmail, EmailVerifyWaitTime: &c.verifyWaitTime, EmailVerifyExpiry: &c.verifyExpiry}
+ newConfig := model.Config{ID: uuid.NewString(), Type: model.ConfigTypeAuth, AppID: authutils.AllApps, OrgID: authutils.AllOrgs, System: true, Data: configData, DateCreated: time.Now().UTC()}
+ err = c.app.storage.InsertConfig(context, newConfig)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionInsert, model.TypeConfig, &logutils.FieldArgs{"type": model.ConfigTypeAuth, "app_id": authutils.AllApps, "org_id": authutils.AllOrgs}, err)
+ }
+ } else {
+ configData, err := model.GetConfigData[model.AuthConfigData](*config)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionParse, model.TypeAuthConfigData, nil, err)
+ }
+
+ updateShouldVerify := configData.EmailShouldVerify == nil || (*configData.EmailShouldVerify != c.verifyEmail)
+ updateVerifyWaitTime := configData.EmailVerifyWaitTime == nil || (*configData.EmailVerifyWaitTime != c.verifyWaitTime)
+ updateVerifyExpiry := configData.EmailVerifyExpiry == nil || (*configData.EmailVerifyExpiry != c.verifyExpiry)
+ if updateShouldVerify || updateVerifyWaitTime || updateVerifyExpiry {
+ configData.EmailShouldVerify = &c.verifyEmail
+ configData.EmailVerifyWaitTime = &c.verifyWaitTime
+ configData.EmailVerifyExpiry = &c.verifyExpiry
+ config.Data = *configData
+
+ err = c.app.storage.UpdateConfig(context, *config)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeConfig, &logutils.FieldArgs{"id": config.ID}, err)
+ }
+ }
+ }
+
+ //3. insert system org if does not exist
systemOrg, err := c.app.storage.FindSystemOrganization()
if err != nil {
return errors.WrapErrorAction(logutils.ActionFind, model.TypeOrganization, nil, err)
@@ -118,7 +154,7 @@ func (c *APIs) storeSystemData() error {
createAccount = true
}
- //3. insert system app and appOrg if they do not exist
+ //4. insert system app and appOrg if they do not exist
systemAdminAppOrgs, err := c.app.storage.FindApplicationsOrganizationsByOrgID(systemOrg.ID)
if err != nil {
return errors.WrapErrorAction(logutils.ActionFind, model.TypeApplicationOrganization, nil, err)
@@ -142,7 +178,7 @@ func (c *APIs) storeSystemData() error {
//insert system admin apporg
supportedAuthTypes := make([]model.AuthTypesSupport, len(systemAdminApp.Types))
for i, appType := range systemAdminApp.Types {
- supportedAuthTypes[i] = model.AuthTypesSupport{AppTypeID: appType.ID, SupportedAuthTypes: []model.SupportedAuthType{{AuthTypeID: emailAuthType.ID, Params: nil}}}
+ supportedAuthTypes[i] = model.AuthTypesSupport{AppTypeID: appType.ID, SupportedAuthTypes: []model.SupportedAuthType{{AuthTypeID: passwordAuthType.ID, Params: nil}}}
}
newDocuments["application_organization"] = uuid.NewString()
@@ -159,7 +195,7 @@ func (c *APIs) storeSystemData() error {
systemAppOrg := systemAdminAppOrgs[0]
- //4. insert api key if does not exist
+ //5. insert api key if does not exist
apiKeys, err := c.Auth.GetApplicationAPIKeys(systemAppOrg.Application.ID)
if err != nil {
return errors.WrapErrorAction(logutils.ActionFind, model.TypeAPIKey, nil, err)
@@ -177,7 +213,7 @@ func (c *APIs) storeSystemData() error {
}
}
- //5. insert all_system_core permission and grant_all_permissions permission if they do not exist
+ //6. insert all_system_core permission and grant_all_permissions permission if they do not exist
requiredPermissions := map[string]string{
model.PermissionAllSystemCore: "Gives access to all admin and system APIs",
model.PermissionGrantAllPermissions: "Gives the ability to grant any permission",
@@ -222,12 +258,12 @@ func (c *APIs) storeSystemData() error {
}
}
- //6. insert system account if needed
+ //7. insert system account if needed
if createAccount {
if c.systemAccountEmail == "" || c.systemAccountPassword == "" {
return errors.ErrorData(logutils.StatusMissing, "initial system account email or password", nil)
}
- newDocuments["account"], err = c.Auth.InitializeSystemAccount(context, *emailAuthType, systemAppOrg, model.PermissionAllSystemCore, c.systemAccountEmail, c.systemAccountPassword, "", c.logger.NewRequestLog(nil))
+ newDocuments["account"], err = c.Auth.InitializeSystemAccount(context, *passwordAuthType, systemAppOrg, model.PermissionAllSystemCore, c.systemAccountEmail, c.systemAccountPassword, "", c.logger.NewRequestLog(nil))
if err != nil {
return errors.WrapErrorAction(logutils.ActionInitialize, "system account", nil, err)
}
@@ -245,7 +281,7 @@ func (c *APIs) storeSystemData() error {
}
fields := logutils.Fields{key: data}
if doc == "auth_type" {
- fields["code"] = auth.AuthTypeEmail
+ fields["code"] = auth.IdentifierTypeEmail
}
c.logger.InfoWithFields(fmt.Sprintf("new system %s created", doc), fields)
}
@@ -254,7 +290,8 @@ func (c *APIs) storeSystemData() error {
}
// NewCoreAPIs creates new CoreAPIs
-func NewCoreAPIs(env string, version string, build string, serviceID string, storage Storage, auth auth.APIs, systemInitSettings map[string]string, verifyEmail bool, logger *logs.Logger) *APIs {
+func NewCoreAPIs(env string, version string, build string, serviceID string, storage Storage, auth auth.APIs, systemInitSettings map[string]string, verifyEmail bool,
+ verifyWaitTime int, verifyExpiry int, logger *logs.Logger) *APIs {
//add application instance
listeners := []ApplicationListener{}
application := application{env: env, version: version, build: build, serviceID: serviceID, storage: storage, listeners: listeners, auth: auth}
@@ -271,7 +308,8 @@ func NewCoreAPIs(env string, version string, build string, serviceID string, sto
coreAPIs := APIs{Services: servicesImpl, Administration: administrationImpl, Encryption: encryptionImpl,
BBs: bbsImpl, TPS: tpsImpl, System: systemImpl, Auth: auth, app: &application, systemAppTypeIdentifier: systemInitSettings["app_type_id"],
systemAppTypeName: systemInitSettings["app_type_name"], systemAPIKey: systemInitSettings["api_key"],
- systemAccountEmail: systemInitSettings["email"], systemAccountPassword: systemInitSettings["password"], verifyEmail: verifyEmail, logger: logger}
+ systemAccountEmail: systemInitSettings["email"], systemAccountPassword: systemInitSettings["password"], verifyEmail: verifyEmail,
+ verifyWaitTime: verifyWaitTime, verifyExpiry: verifyExpiry, logger: logger}
return &coreAPIs
}
diff --git a/core/apis_test.go b/core/apis_test.go
index b78994fbc..25c4d64e7 100644
--- a/core/apis_test.go
+++ b/core/apis_test.go
@@ -29,7 +29,7 @@ import (
)
func buildTestCoreAPIs(storage core.Storage) *core.APIs {
- return core.NewCoreAPIs("local", "1.1.1", "build", "core", storage, nil, nil, false, nil)
+ return core.NewCoreAPIs("local", "1.1.1", "build", "core", storage, nil, nil, false, 30, 24, nil)
}
//Services
@@ -83,7 +83,7 @@ func TestAdmGetTest(t *testing.T) {
func TestAdmCreateConfig(t *testing.T) {
anyConfig := mock.AnythingOfType("model.Config")
storage := genmocks.Storage{}
- storage.On("InsertConfig", anyConfig).Return(nil)
+ storage.On("InsertConfig", nil, anyConfig).Return(nil)
coreAPIs := buildTestCoreAPIs(&storage)
@@ -96,7 +96,7 @@ func TestAdmCreateConfig(t *testing.T) {
//second case - error
storage2 := genmocks.Storage{}
- storage2.On("InsertConfig", anyConfig).Return(errors.New("error occured"))
+ storage2.On("InsertConfig", nil, anyConfig).Return(errors.New("error occured"))
coreAPIs = buildTestCoreAPIs(&storage2)
diff --git a/core/app_administration.go b/core/app_administration.go
index 958fe3485..7aa48487b 100644
--- a/core/app_administration.go
+++ b/core/app_administration.go
@@ -231,7 +231,7 @@ func (app *application) admCreateConfig(config model.Config, claims *tokenauth.C
config.ID = uuid.NewString()
config.DateCreated = time.Now().UTC()
- err = app.storage.InsertConfig(config)
+ err = app.storage.InsertConfig(nil, config)
if err != nil {
return nil, errors.WrapErrorAction(logutils.ActionInsert, model.TypeConfig, nil, err)
}
@@ -261,11 +261,8 @@ func (app *application) admUpdateConfig(config model.Config, claims *tokenauth.C
return errors.WrapErrorAction(logutils.ActionValidate, "config access", nil, err)
}
- now := time.Now().UTC()
config.ID = oldConfig.ID
- config.DateUpdated = &now
-
- err = app.storage.UpdateConfig(config)
+ err = app.storage.UpdateConfig(nil, config)
if err != nil {
return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeConfig, nil, err)
}
diff --git a/core/app_services.go b/core/app_services.go
index a59094325..5c55ab590 100644
--- a/core/app_services.go
+++ b/core/app_services.go
@@ -33,6 +33,7 @@ func (app *application) serGetProfile(accountID string) (*model.Profile, error)
//get the profile for the account
profile := account.Profile
+ profile.Accounts = []model.Account{*account}
return &profile, nil
}
diff --git a/core/app_shared.go b/core/app_shared.go
index 55ee9517f..a2f0320e7 100644
--- a/core/app_shared.go
+++ b/core/app_shared.go
@@ -17,6 +17,7 @@ package core
import (
"core-building-block/core/model"
"core-building-block/driven/storage"
+ "core-building-block/utils"
"github.com/rokwire/logging-library-go/v2/errors"
"github.com/rokwire/logging-library-go/v2/logutils"
@@ -88,25 +89,19 @@ func (app *application) sharedGetAccountsCountByParams(searchParams map[string]i
func (app *application) sharedUpdateAccountUsername(accountID string, appID string, orgID string, username string) error {
if username == "" {
- err := app.storage.UpdateAccountUsername(nil, accountID, username)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeAccountUsername, nil, err)
- }
+ return errors.ErrorData(logutils.StatusMissing, model.TypeAccountUsername, nil)
+ }
- return nil
+ appOrg, err := app.storage.FindApplicationOrganization(appID, orgID)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionFind, model.TypeApplicationOrganization, &logutils.FieldArgs{"app_id": appID, "org_id": orgID}, err)
+ }
+ if appOrg == nil {
+ return errors.ErrorData(logutils.StatusMissing, model.TypeApplicationOrganization, &logutils.FieldArgs{"app_id": appID, "org_id": orgID})
}
transaction := func(context storage.TransactionContext) error {
- //1. find the app/org
- appOrg, err := app.storage.FindApplicationOrganization(appID, orgID)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionFind, model.TypeApplicationOrganization, &logutils.FieldArgs{"app_id": appID, "org_id": orgID}, err)
- }
- if appOrg == nil {
- return errors.ErrorData(logutils.StatusMissing, model.TypeApplicationOrganization, &logutils.FieldArgs{"app_id": appID, "org_id": orgID})
- }
-
- //2. check if any accounts in the app/org use the username
+ //1. check if any accounts in the app/org use the username
accounts, err := app.storage.FindAccountsByUsername(context, appOrg, username)
if err != nil {
return errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
@@ -118,10 +113,10 @@ func (app *application) sharedUpdateAccountUsername(accountID string, appID stri
return nil
}
}
- return errors.ErrorData(logutils.StatusInvalid, model.TypeAccountUsername, logutils.StringArgs(username+" taken"))
+ return errors.ErrorData(logutils.StatusInvalid, model.TypeAccountUsername, logutils.StringArgs(username+" taken")).SetStatus(utils.ErrorStatusUsernameTaken)
}
- //3. update the username
+ //2. update the username
err = app.storage.UpdateAccountUsername(context, accountID, username)
if err != nil {
return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeAccountUsername, nil, err)
diff --git a/core/app_system.go b/core/app_system.go
index a04293ac7..86c0acaa3 100644
--- a/core/app_system.go
+++ b/core/app_system.go
@@ -47,7 +47,7 @@ func (app *application) sysCreateApplicationOrganization(appOrg model.Applicatio
appOrgID, _ := uuid.NewUUID()
appOrg.ID = appOrgID.String()
- appOrg.DateCreated = time.Now()
+ appOrg.DateCreated = time.Now().UTC()
insertedAppOrg, err := app.storage.InsertApplicationOrganization(nil, appOrg)
if err != nil {
@@ -68,7 +68,7 @@ func (app *application) sysUpdateApplicationOrganization(appOrg model.Applicatio
}
func (app *application) sysCreateOrganization(name string, requestType string, organizationDomains []string) (*model.Organization, error) {
- now := time.Now()
+ now := time.Now().UTC()
orgConfig := model.OrganizationConfig{ID: uuid.NewString(), Domains: organizationDomains, DateCreated: now}
organization := model.Organization{ID: uuid.NewString(), Name: name, Type: requestType, Config: orgConfig, DateCreated: now}
@@ -124,7 +124,7 @@ func (app *application) sysGetApplication(ID string) (*model.Application, error)
}
func (app *application) sysCreateApplication(name string, multiTenant bool, admin bool, sharedIdentities bool, appTypes []model.ApplicationType) (*model.Application, error) {
- now := time.Now()
+ now := time.Now().UTC()
// application
for i, at := range appTypes {
@@ -212,7 +212,7 @@ func (app *application) sysGetApplications() ([]model.Application, error) {
func (app *application) sysCreatePermission(name string, description *string, serviceID *string, assigners *[]string) (*model.Permission, error) {
id, _ := uuid.NewUUID()
- now := time.Now()
+ now := time.Now().UTC()
serviceIDVal := ""
if serviceID != nil {
serviceIDVal = *serviceID
diff --git a/core/auth/apis.go b/core/auth/apis.go
index 1dfc49b59..0bf234747 100644
--- a/core/auth/apis.go
+++ b/core/auth/apis.go
@@ -18,6 +18,7 @@ import (
"core-building-block/core/model"
"core-building-block/driven/storage"
"core-building-block/utils"
+ "encoding/json"
"fmt"
"strings"
"time"
@@ -50,37 +51,39 @@ func (a *Auth) GetHost() string {
// Login logs a user into a specific application using the specified credentials and authentication method.
// The authentication method must be one of the supported for the application.
//
-// Input:
-// ipAddress (string): Client's IP address
-// deviceType (string): "mobile" or "web" or "desktop" etc
-// deviceOS (*string): Device OS
-// deviceID (*string): Device ID
-// authenticationType (string): Name of the authentication method for provided creds (eg. "email", "username", "illinois_oidc")
-// creds (string): Credentials/JSON encoded credential structure defined for the specified auth type
-// apiKey (string): API key to validate the specified app
-// appTypeIdentifier (string): identifier of the app type/client that the user is logging in from
-// orgID (string): ID of the organization that the user is logging in
-// params (string): JSON encoded params defined by specified auth type
-// profile (Profile): Account profile
-// preferences (map): Account preferences
-// admin (bool): Is this an admin login?
-// l (*logs.Log): Log object pointer for request
-// Returns:
-// Message (*string): message
-// Login session (*LoginSession): Signed ROKWIRE access token to be used to authorize future requests
-// Access token (string): Signed ROKWIRE access token to be used to authorize future requests
-// Refresh Token (string): Refresh token that can be sent to refresh the access token once it expires
-// AccountAuthType (AccountAuthType): AccountAuthType object for authenticated user
-// Params (interface{}): authType-specific set of parameters passed back to client
-// State (string): login state used if account is enrolled in MFA
-// MFA types ([]model.MFAType): list of MFA types account is enrolled in
+// Input:
+// ipAddress (string): Client's IP address
+// deviceType (string): "mobile" or "web" or "desktop" etc
+// deviceOS (*string): Device OS
+// deviceID (*string): Device ID
+// authenticationType (string): Name of the authentication method for provided creds (eg. "password", "code", "illinois_oidc")
+// creds (string): Credentials/JSON encoded credential structure defined for the specified auth type
+// apiKey (string): API key to validate the specified app
+// appTypeIdentifier (string): identifier of the app type/client that the user is logging in from
+// orgID (string): ID of the organization that the user is logging in
+// params (string): JSON encoded params defined by specified auth type
+// clientVersion(*string): Most recent client version
+// profile (Profile): Account profile
+// preferences (map): Account preferences
+// accountIdentifierID (*string): UUID of account identifier, meant to be used after using SignInOptions
+// admin (bool): Is this an admin login?
+// l (*logs.Log): Log object pointer for request
+// Returns:
+// Response parameters (map): any messages or parameters to send in response when requiring identifier verification and/or NOT logging in the user
+// Login session (*LoginSession): Signed ROKWIRE access token to be used to authorize future requests
+// Access token (string): Signed ROKWIRE access token to be used to authorize future requests
+// Refresh Token (string): Refresh token that can be sent to refresh the access token once it expires
+// AccountAuthType (AccountAuthType): AccountAuthType object for authenticated user
+// Params (interface{}): authType-specific set of parameters passed back to client
+// State (string): login state used if account is enrolled in MFA
+// MFA types ([]model.MFAType): list of MFA types account is enrolled in
func (a *Auth) Login(ipAddress string, deviceType string, deviceOS *string, deviceID *string, authenticationType string, creds string, apiKey string,
appTypeIdentifier string, orgID string, params string, clientVersion *string, profile model.Profile, privacy model.Privacy, preferences map[string]interface{},
- username string, admin bool, l *logs.Log) (*string, *model.LoginSession, []model.MFAType, error) {
+ accountIdentifierID *string, admin bool, l *logs.Log) (map[string]interface{}, *model.LoginSession, []model.MFAType, error) {
//TODO - analyse what should go in one transaction
//validate if the provided auth type is supported by the provided application and organization
- authType, appType, appOrg, err := a.validateAuthType(authenticationType, appTypeIdentifier, orgID)
+ authType, appType, appOrg, err := a.validateAuthType(authenticationType, &appTypeIdentifier, nil, orgID)
if err != nil || authType == nil {
return nil, nil, nil, errors.WrapErrorAction(logutils.ActionValidate, model.TypeAuthType, nil, err)
}
@@ -98,61 +101,49 @@ func (a *Auth) Login(ipAddress string, deviceType string, deviceOS *string, devi
return nil, nil, nil, errors.WrapErrorData(logutils.StatusInvalid, model.TypeAPIKey, nil, err)
}
- username = strings.TrimSpace(strings.ToLower(username))
-
anonymous := false
sub := ""
- var message string
- var accountAuthType *model.AccountAuthType
+ var account *model.Account
var responseParams map[string]interface{}
- var externalIDs map[string]string
var mfaTypes []model.MFAType
var state string
//get the auth type implementation for the auth type
- if authType.IsAnonymous && !admin {
+ if authType.AuthType.IsAnonymous && !admin {
anonymous = true
anonymousID := ""
- var account *model.Account
- anonymousID, account, responseParams, err = a.applyAnonymousAuthType(*authType, creds)
+ anonymousID, account, responseParams, err = a.applyAnonymousAuthType(authType.AuthType, creds)
if err != nil {
return nil, nil, nil, errors.WrapErrorAction(logutils.ActionApply, typeAnonymousAuthType, logutils.StringArgs("user"), err)
}
sub = anonymousID
-
- if account != nil {
- accountAuthType = &model.AccountAuthType{Account: *account}
- }
- } else if authType.IsExternal {
- accountAuthType, responseParams, mfaTypes, externalIDs, err = a.applyExternalAuthType(*authType, *appType, *appOrg, creds, params, clientVersion, profile, privacy, preferences, username, admin, l)
+ } else if authType.AuthType.IsExternal {
+ responseParams, account, mfaTypes, err = a.applyExternalAuthType(*authType, *appType, *appOrg, creds, params, clientVersion, profile, privacy, preferences, admin, l)
if err != nil {
return nil, nil, nil, errors.WrapErrorAction(logutils.ActionApply, typeExternalAuthType, logutils.StringArgs("user"), err)
}
- sub = accountAuthType.Account.ID
+ sub = account.ID
} else {
- message, accountAuthType, mfaTypes, externalIDs, err = a.applyAuthType(*authType, *appOrg, creds, params, clientVersion, profile, privacy, preferences, username, admin, l)
+ responseParams, account, mfaTypes, err = a.applyAuthType(*authType, *appOrg, appType, creds, params, clientVersion, profile, privacy, preferences, accountIdentifierID, admin, l)
if err != nil {
return nil, nil, nil, errors.WrapErrorAction(logutils.ActionApply, model.TypeAuthType, logutils.StringArgs("user"), err)
}
//message
- if len(message) > 0 {
- return &message, nil, nil, nil
+ if responseParams != nil {
+ return responseParams, nil, nil, nil
}
- sub = accountAuthType.Account.ID
+ sub = account.ID
//the credentials are valid
}
//check if account is enrolled in MFA
- if !authType.IgnoreMFA && len(mfaTypes) > 0 {
- state, err = utils.GenerateRandomString(loginStateLength)
- if err != nil {
- return nil, nil, nil, errors.WrapErrorAction(logutils.ActionGenerate, "login state", nil, err)
- }
+ if !authType.AuthType.IgnoreMFA && len(mfaTypes) > 0 {
+ state = utils.GenerateRandomString(loginStateLength)
}
//clear the expired sessions for the identifier - user or anonymous
@@ -162,7 +153,7 @@ func (a *Auth) Login(ipAddress string, deviceType string, deviceOS *string, devi
}
//now we are ready to apply login for the user or anonymous
- loginSession, err := a.applyLogin(anonymous, sub, *authType, *appOrg, accountAuthType, *appType, externalIDs, ipAddress, deviceType, deviceOS, deviceID, clientVersion, responseParams, state, l)
+ loginSession, err := a.applyLogin(anonymous, sub, authType.AuthType, *appOrg, account, *appType, ipAddress, deviceType, deviceOS, deviceID, clientVersion, responseParams, state, l)
if err != nil {
return nil, nil, nil, errors.WrapErrorAction(logutils.ActionApply, "login", logutils.StringArgs("user"), err)
}
@@ -197,15 +188,21 @@ func (a *Auth) Logout(appID string, orgID string, currentAccountID string, sessi
// The authentication method must be one of the supported for the application.
//
// Input:
-// authenticationType (string): Name of the authentication method for provided creds (eg. "email", "username", "illinois_oidc")
-// userIdentifier (string): User identifier for the specified auth type
+// identifierJSON (string): json string representing the user identifier and its type
// apiKey (string): API key to validate the specified app
// appTypeIdentifier (string): identifier of the app type/client that the user is logging in from
// orgID (string): ID of the organization that the user is logging in
+// authenticationType (*string): Optional authentication type (for backwards compatibility)
+// userIdentifier (*string): Optional identifier for the given authentication type (for backwards compatibility)
// Returns:
// accountExisted (bool): valid when error is nil
-func (a *Auth) AccountExists(authenticationType string, userIdentifier string, apiKey string, appTypeIdentifier string, orgID string) (bool, error) {
- account, _, err := a.getAccount(authenticationType, userIdentifier, apiKey, appTypeIdentifier, orgID)
+func (a *Auth) AccountExists(identifierJSON string, apiKey string, appTypeIdentifier string, orgID string, authenticationType *string, userIdentifier *string) (bool, error) {
+ identifierImpl := a.getIdentifierTypeImpl(identifierJSON, authenticationType, userIdentifier)
+ if identifierImpl == nil {
+ return false, errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, nil)
+ }
+
+ account, err := a.getAccount(identifierImpl.getCode(), identifierImpl.getIdentifier(), apiKey, appTypeIdentifier, orgID)
if err != nil {
return false, errors.WrapErrorAction(logutils.ActionGet, model.TypeAccount, nil, err)
}
@@ -217,47 +214,116 @@ func (a *Auth) AccountExists(authenticationType string, userIdentifier string, a
// The authentication method must be one of the supported for the application.
//
// Input:
-// authenticationType (string): Name of the authentication method for provided creds (eg. "email", "username", "illinois_oidc")
-// userIdentifier (string): User identifier for the specified auth type
+// identifierJSON (string): json string representing the user identifier and its type
// apiKey (string): API key to validate the specified app
-// appTypeIdentifier (string): identifier of the app type/client being used
-// orgID (string): ID of the organization being used
+// appTypeIdentifier (string): identifier of the app type/client that the user is logging in from
+// orgID (string): ID of the organization that the user is logging in
+// authenticationType (*string): Optional authentication type (for backwards compatibility)
+// userIdentifier (*string): Optional identifier for the given authentication type (for backwards compatibility)
// Returns:
// canSignIn (bool): valid when error is nil
-func (a *Auth) CanSignIn(authenticationType string, userIdentifier string, apiKey string, appTypeIdentifier string, orgID string) (bool, error) {
- account, authTypeID, err := a.getAccount(authenticationType, userIdentifier, apiKey, appTypeIdentifier, orgID)
+func (a *Auth) CanSignIn(identifierJSON string, apiKey string, appTypeIdentifier string, orgID string, authenticationType *string, userIdentifier *string) (bool, error) {
+ identifierImpl := a.getIdentifierTypeImpl(identifierJSON, authenticationType, userIdentifier)
+ if identifierImpl == nil {
+ return false, errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, nil)
+ }
+
+ code := identifierImpl.getCode()
+ identifier := identifierImpl.getIdentifier()
+
+ account, err := a.getAccount(code, identifier, apiKey, appTypeIdentifier, orgID)
if err != nil {
return false, errors.WrapErrorAction(logutils.ActionGet, model.TypeAccount, nil, err)
}
- return a.canSignIn(account, authTypeID, userIdentifier), nil
+ return a.canSignIn(account, code, identifier), nil
}
// CanLink checks if a user can link a new auth type
// The authentication method must be one of the supported for the application.
//
// Input:
-// authenticationType (string): Name of the authentication method for provided creds (eg. "email", "username", "illinois_oidc")
-// userIdentifier (string): User identifier for the specified auth type
+// identifierJSON (string): json string representing the user identifier and its type
// apiKey (string): API key to validate the specified app
-// appTypeIdentifier (string): identifier of the app type/client being used
-// orgID (string): ID of the organization being used
+// appTypeIdentifier (string): identifier of the app type/client that the user is logging in from
+// orgID (string): ID of the organization that the user is logging in
+// authenticationType (*string): Optional authentication type (for backwards compatibility)
+// userIdentifier (*string): Optional identifier for the given authentication type (for backwards compatibility)
// Returns:
// canLink (bool): valid when error is nil
-func (a *Auth) CanLink(authenticationType string, userIdentifier string, apiKey string, appTypeIdentifier string, orgID string) (bool, error) {
- account, authTypeID, err := a.getAccount(authenticationType, userIdentifier, apiKey, appTypeIdentifier, orgID)
+func (a *Auth) CanLink(identifierJSON string, apiKey string, appTypeIdentifier string, orgID string, authenticationType *string, userIdentifier *string) (bool, error) {
+ identifierImpl := a.getIdentifierTypeImpl(identifierJSON, authenticationType, userIdentifier)
+ if identifierImpl == nil {
+ return false, errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, nil)
+ }
+
+ code := identifierImpl.getCode()
+ identifier := identifierImpl.getIdentifier()
+
+ account, err := a.getAccount(code, identifier, apiKey, appTypeIdentifier, orgID)
if err != nil {
return false, errors.WrapErrorAction(logutils.ActionGet, model.TypeAccount, nil, err)
}
if account != nil {
- aat := account.GetAccountAuthType(authTypeID, userIdentifier)
- return (aat != nil && aat.Unverified), nil
+ ai := account.GetAccountIdentifier(code, identifier)
+ if authenticationType == nil && userIdentifier == nil {
+ // if not an old client, treat as a request to check if can link identifier
+ return (ai != nil && !ai.Verified), nil
+ }
}
+ // either there is no account with the provided identifier, or
+ // old client, so treat as request to check if can link identifier OR auth type (can always attempt to link an auth type)
return true, nil
}
+// SignInOptions returns the identifiers and auth types that may be used to sign in to an account
+//
+// Input:
+// userIdentifier (string): User identifier for the specified auth type
+// apiKey (string): API key to validate the specified app
+// appTypeIdentifier (string): identifier of the app type/client being used
+// orgID (string): ID of the organization being used
+// Returns:
+// identifiers ([]model.AccountIdentifier): account identifiers that may be used for sign-in
+// authTypes ([]model.AccountAuthType): account auth types that may be used for sign-in
+func (a *Auth) SignInOptions(identifierJSON string, apiKey string, appTypeIdentifier string, orgID string, authenticationType *string, userIdentifier *string, l *logs.Log) ([]model.AccountIdentifier, []model.AccountAuthType, error) {
+ identifierImpl := a.getIdentifierTypeImpl(identifierJSON, authenticationType, userIdentifier)
+ if identifierImpl == nil {
+ return nil, nil, errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, nil)
+ }
+
+ code := identifierImpl.getCode()
+ identifier := identifierImpl.getIdentifier()
+
+ account, err := a.getAccount(code, identifier, apiKey, appTypeIdentifier, orgID)
+ if err != nil {
+ return nil, nil, errors.WrapErrorAction(logutils.ActionGet, model.TypeAccount, nil, err)
+ }
+ if account == nil {
+ return nil, nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccount, nil)
+ }
+
+ identifiers := account.GetVerifiedAccountIdentifiers()
+ for i, id := range identifiers {
+ if id.Sensitive {
+ idImpl := a.getIdentifierTypeImpl("", &id.Code, &id.Identifier)
+ if idImpl == nil {
+ return nil, nil, errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, &logutils.FieldArgs{"code": id.Code})
+ }
+
+ masked, err := idImpl.maskIdentifier()
+ if err != nil {
+ l.Errorf("error masking identifier for sign-in options: %v", err)
+ continue
+ }
+ identifiers[i].Identifier = masked
+ }
+ }
+ return identifiers, account.AuthTypes, nil
+}
+
// Refresh refreshes an access token using a refresh token
//
// Input:
@@ -330,11 +396,12 @@ func (a *Auth) Refresh(refreshToken string, apiKey string, clientVersion *string
authType := loginSession.AuthType.Code
anonymous := loginSession.Anonymous
- uid := ""
name := ""
email := ""
phone := ""
+ username := ""
permissions := []string{}
+ externalIDs := make(map[string]string)
// - generate new params and update the account if needed(if external auth type)
if loginSession.AuthType.IsExternal {
@@ -350,33 +417,53 @@ func (a *Auth) Refresh(refreshToken string, apiKey string, clientVersion *string
return nil, errors.WrapErrorAction(logutils.ActionRefresh, "external auth type", nil, err)
}
+ if loginSession.Account == nil {
+ return nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccount, &logutils.FieldArgs{"session_id": loginSession.ID, "anonymous": false})
+ }
+
+ aats := loginSession.Account.GetAccountAuthTypes(loginSession.AuthType.Code)
+ if len(aats) != 1 {
+ return nil, errors.ErrorData(logutils.StatusInvalid, model.TypeAccountAuthType, &logutils.FieldArgs{"code": loginSession.AuthType.Code, "count": len(aats)})
+ }
+
//check if need to update the account data
- newAccount, err := a.updateExternalUserIfNeeded(*loginSession.AccountAuthType, *externalUser, loginSession.AuthType, loginSession.AppOrg, externalCreds, l)
+ newAccount, err := a.updateExternalUserIfNeeded(aats[0], *externalUser, loginSession.AuthType, loginSession.AppOrg, externalCreds, l)
if err != nil {
return nil, errors.WrapErrorAction(logutils.ActionUpdate, model.TypeExternalSystemUser, logutils.StringArgs("refresh"), err)
}
loginSession.Params = refreshedData //assign the refreshed data
if newAccount != nil {
- loginSession.ExternalIDs = newAccount.ExternalIDs
+ for _, external := range newAccount.GetExternalAccountIdentifiers() {
+ externalIDs[external.Code] = external.Identifier
+ }
}
}
scopes := []string{authorization.ScopeGlobal}
if !anonymous {
- accountAuthType := loginSession.AccountAuthType
- if accountAuthType == nil {
- l.Infof("for some reasons account auth type is null for not anonymous login - %s", loginSession.ID)
- return nil, errors.ErrorAction("for some reasons account auth type is null for not anonymous login", "", nil)
- }
- uid = accountAuthType.Identifier
- name = accountAuthType.Account.Profile.GetFullName()
- email = accountAuthType.Account.Profile.Email
- phone = accountAuthType.Account.Profile.Phone
- permissions = accountAuthType.Account.GetPermissionNames()
- scopes = append(scopes, accountAuthType.Account.GetScopes()...)
- }
- claims := a.getStandardClaims(sub, uid, name, email, phone, rokwireTokenAud, orgID, appID, authType, loginSession.ExternalIDs, nil, anonymous, false, loginSession.AppOrg.Application.Admin, loginSession.AppOrg.Organization.System, false, true, loginSession.ID)
+ if loginSession.Account == nil {
+ return nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccount, &logutils.FieldArgs{"session_id": loginSession.ID, "anonymous": false})
+ }
+ if emailIdentifier := loginSession.Account.GetAccountIdentifier(IdentifierTypeEmail, ""); emailIdentifier != nil {
+ email = emailIdentifier.Identifier
+ }
+ if phoneIdentifier := loginSession.Account.GetAccountIdentifier(IdentifierTypePhone, ""); phoneIdentifier != nil {
+ phone = phoneIdentifier.Identifier
+ }
+ if usernameIdentifier := loginSession.Account.GetAccountIdentifier(IdentifierTypeUsername, ""); usernameIdentifier != nil {
+ username = usernameIdentifier.Identifier
+ }
+ name = loginSession.Account.Profile.GetFullName()
+ permissions = loginSession.Account.GetPermissionNames()
+ scopes = append(scopes, loginSession.Account.GetScopes()...)
+ if len(externalIDs) == 0 {
+ for _, external := range loginSession.Account.GetExternalAccountIdentifiers() {
+ externalIDs[external.Code] = external.Identifier
+ }
+ }
+ }
+ claims := a.getStandardClaims(sub, name, email, phone, username, rokwireTokenAud, orgID, appID, authType, externalIDs, nil, anonymous, false, loginSession.AppOrg.Application.Admin, loginSession.AppOrg.Organization.System, false, true, loginSession.ID)
accessToken, err := a.buildAccessToken(claims, strings.Join(permissions, ","), strings.Join(scopes, " "))
if err != nil {
l.Infof("error generating acccess token on refresh - %s", refreshToken)
@@ -384,11 +471,7 @@ func (a *Auth) Refresh(refreshToken string, apiKey string, clientVersion *string
}
loginSession.AccessToken = accessToken //set the generated token
// - generate new refresh token
- refreshToken, err = a.buildRefreshToken()
- if err != nil {
- l.Infof("error generating refresh token on refresh - %s", refreshToken)
- return nil, errors.WrapErrorAction(logutils.ActionCreate, logutils.TypeToken, nil, err)
- }
+ refreshToken = utils.GenerateRandomString(refreshTokenLength)
if loginSession.RefreshTokens == nil {
loginSession.RefreshTokens = make([]string, 0)
}
@@ -421,7 +504,7 @@ func (a *Auth) Refresh(refreshToken string, apiKey string, clientVersion *string
// GetLoginURL returns a pre-formatted login url for SSO providers
//
// Input:
-// authenticationType (string): Name of the authentication method for provided creds (eg. "email", "username", "illinois_oidc")
+// authenticationType (string): Name of the authentication method for provided creds (eg. "illinois_oidc")
// appTypeIdentifier (string): Identifier of the app type/client that the user is logging in from
// orgID (string): ID of the organization that the user is logging in
// redirectURI (string): Registered redirect URI where client will receive response
@@ -432,7 +515,7 @@ func (a *Auth) Refresh(refreshToken string, apiKey string, clientVersion *string
// Params (map[string]interface{}): Params to be sent in subsequent request (if necessary)
func (a *Auth) GetLoginURL(authenticationType string, appTypeIdentifier string, orgID string, redirectURI string, apiKey string, l *logs.Log) (string, map[string]interface{}, error) {
//validate if the provided auth type is supported by the provided application and organization
- authType, appType, _, err := a.validateAuthType(authenticationType, appTypeIdentifier, orgID)
+ authType, appType, _, err := a.validateAuthType(authenticationType, &appTypeIdentifier, nil, orgID)
if err != nil {
return "", nil, errors.WrapErrorAction(logutils.ActionValidate, model.TypeAuthType, nil, err)
}
@@ -444,13 +527,13 @@ func (a *Auth) GetLoginURL(authenticationType string, appTypeIdentifier string,
}
//get the auth type implementation for the auth type
- authImpl, err := a.getExternalAuthTypeImpl(*authType)
+ authImpl, err := a.getExternalAuthTypeImpl(authType.AuthType)
if err != nil {
return "", nil, errors.WrapErrorAction(logutils.ActionLoadCache, model.TypeAuthType, nil, err)
}
//get login URL
- loginURL, params, err := authImpl.getLoginURL(*authType, *appType, redirectURI, l)
+ loginURL, params, err := authImpl.getLoginURL(authType.AuthType, *appType, redirectURI, l)
if err != nil {
return "", nil, errors.WrapErrorAction(logutils.ActionGet, "login url", nil, err)
}
@@ -564,55 +647,66 @@ func (a *Auth) LoginMFA(apiKey string, accountID string, sessionID string, ident
}
// CreateAdminAccount creates an account for a new admin user
-func (a *Auth) CreateAdminAccount(authenticationType string, appID string, orgID string, identifier string, profile model.Profile, privacy model.Privacy, username string,
- permissions []string, roleIDs []string, groupIDs []string, scopes []string, creatorPermissions []string, clientVersion *string, l *logs.Log) (*model.Account, map[string]interface{}, error) {
- //TODO: add admin authentication policies that specify which auth types may be used for each app org
- if authenticationType != AuthTypeOidc && authenticationType != AuthTypeEmail && !strings.HasSuffix(authenticationType, "_oidc") {
- return nil, nil, errors.ErrorData(logutils.StatusInvalid, "auth type", nil)
- }
-
+func (a *Auth) CreateAdminAccount(authenticationType string, appID string, orgID string, identifierJSON string, profile model.Profile, privacy model.Privacy, permissions []string,
+ roleIDs []string, groupIDs []string, scopes []string, creatorPermissions []string, clientVersion *string, l *logs.Log) (*model.Account, map[string]interface{}, error) {
// check if the provided auth type is supported by the provided application and organization
- authType, appOrg, err := a.validateAuthTypeForAppOrg(authenticationType, appID, orgID)
+ supportedAuthType, _, appOrg, err := a.validateAuthType(authenticationType, nil, &appID, orgID)
if err != nil {
return nil, nil, errors.WrapErrorAction(logutils.ActionValidate, model.TypeAuthType, nil, err)
}
+ //TODO: add admin authentication policies that specify which auth types may be used for each app org
+ if supportedAuthType.AuthType.Code != AuthTypeOidc && supportedAuthType.AuthType.Code != AuthTypePassword && !strings.HasSuffix(supportedAuthType.AuthType.Code, "_oidc") {
+ return nil, nil, errors.ErrorData(logutils.StatusInvalid, model.TypeAuthType, nil)
+ }
+
+ identifierImpl := a.getIdentifierTypeImpl(identifierJSON, nil, nil)
+ if identifierImpl == nil {
+ return nil, nil, errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, nil)
+ }
+ identifier := identifierImpl.getIdentifier()
+
// create account
- var accountAuthType *model.AccountAuthType
var newAccount *model.Account
var params map[string]interface{}
transaction := func(context storage.TransactionContext) error {
//1. check if the user exists
- account, err := a.storage.FindAccount(context, appOrg.ID, authType.ID, identifier)
+ account, err := a.storage.FindAccount(context, appOrg.ID, identifierImpl.getCode(), identifier)
if err != nil {
return errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
}
if account != nil {
- return errors.ErrorData(logutils.StatusFound, model.TypeAccount, &logutils.FieldArgs{"app_org_id": appOrg.ID, "auth_type": authType.Code, "identifier": identifier})
+ return errors.ErrorData(logutils.StatusFound, model.TypeAccount, &logutils.FieldArgs{"app_org_id": appOrg.ID, "identifier": identifier})
}
//2. account does not exist, so apply sign up
profile.DateCreated = time.Now().UTC()
- if authType.IsExternal {
- externalUser := model.ExternalSystemUser{Identifier: identifier}
- accountAuthType, err = a.applySignUpAdminExternal(context, *authType, *appOrg, externalUser, profile, privacy, username, permissions, roleIDs, groupIDs, scopes, creatorPermissions, clientVersion, l)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionRegister, "admin user", &logutils.FieldArgs{"auth_type": authType.Code, "identifier": identifier}, err)
+ if supportedAuthType.AuthType.IsExternal {
+ identityProviderID, _ := supportedAuthType.AuthType.Params["identity_provider"].(string)
+ identityProviderSetting := appOrg.FindIdentityProviderSetting(identityProviderID)
+ if identityProviderSetting == nil {
+ return errors.ErrorData(logutils.StatusMissing, model.TypeIdentityProviderConfig, &logutils.FieldArgs{"app_org": appOrg.ID, "identity_provider_id": identityProviderID})
}
- } else {
- authImpl, err := a.getAuthTypeImpl(*authType)
+
+ externalIDs := make(map[string]string)
+ for k, v := range identityProviderSetting.ExternalIDFields {
+ if v == identityProviderSetting.UserIdentifierField {
+ externalIDs[k] = identifier
+ break
+ }
+ }
+ externalUser := model.ExternalSystemUser{Identifier: identifier, ExternalIDs: externalIDs, SensitiveExternalIDs: identityProviderSetting.SensitiveExternalIDs}
+ newAccount, err = a.applySignUpAdminExternal(context, *supportedAuthType, *appOrg, externalUser, profile, privacy, permissions, roleIDs, groupIDs, scopes, creatorPermissions, clientVersion, l)
if err != nil {
- return errors.WrapErrorAction(logutils.ActionLoadCache, typeExternalAuthType, nil, err)
+ return errors.WrapErrorAction(logutils.ActionRegister, "admin user", &logutils.FieldArgs{"auth_type": supportedAuthType.AuthType.Code, "identifier": identifier}, err)
}
-
- profile.Email = identifier
- params, accountAuthType, err = a.applySignUpAdmin(context, authImpl, account, *authType, *appOrg, identifier, "", profile, privacy, username, permissions, roleIDs, groupIDs, scopes, creatorPermissions, clientVersion, l)
+ } else {
+ params, newAccount, err = a.signUpNewAccount(context, identifierImpl, *supportedAuthType, *appOrg, nil, "", "", clientVersion, profile, privacy, nil, permissions, roleIDs, groupIDs, scopes, creatorPermissions, l)
if err != nil {
- return errors.WrapErrorAction(logutils.ActionRegister, "admin user", &logutils.FieldArgs{"auth_type": authType.Code, "identifier": identifier}, err)
+ return errors.WrapErrorAction(logutils.ActionRegister, "admin user", &logutils.FieldArgs{"auth_type": supportedAuthType.AuthType.Code, "identifier": identifier}, err)
}
}
- newAccount = &accountAuthType.Account
return nil
}
@@ -625,40 +719,39 @@ func (a *Auth) CreateAdminAccount(authenticationType string, appID string, orgID
}
// UpdateAdminAccount updates an existing user's account with new permissions, roles, and groups
-func (a *Auth) UpdateAdminAccount(authenticationType string, appID string, orgID string, identifier string, permissions []string, roleIDs []string,
+func (a *Auth) UpdateAdminAccount(authenticationType string, appID string, orgID string, identifierJSON string, permissions []string, roleIDs []string,
groupIDs []string, scopes []string, updaterPermissions []string, l *logs.Log) (*model.Account, map[string]interface{}, error) {
+ // check if the provided auth type is supported by the provided application and organization
+ supportedAuthType, _, appOrg, err := a.validateAuthType(authenticationType, nil, &appID, orgID)
+ if err != nil {
+ return nil, nil, errors.WrapErrorAction(logutils.ActionValidate, model.TypeAuthType, nil, err)
+ }
+
//TODO: when elevating existing accounts to application level admin, need to enforce any authentication policies set up for the app org
// when demoting from application level admin to standard user, may want to inform user of applicable authentication policy changes
-
- if authenticationType != AuthTypeOidc && authenticationType != AuthTypeEmail && !strings.HasSuffix(authenticationType, "_oidc") {
+ if supportedAuthType.AuthType.Code != AuthTypeOidc && supportedAuthType.AuthType.Code != AuthTypePassword && !strings.HasSuffix(supportedAuthType.AuthType.Code, "_oidc") {
return nil, nil, errors.ErrorData(logutils.StatusInvalid, "auth type", nil)
}
- // check if the provided auth type is supported by the provided application and organization
- authType, appOrg, err := a.validateAuthTypeForAppOrg(authenticationType, appID, orgID)
- if err != nil {
- return nil, nil, errors.WrapErrorAction(logutils.ActionValidate, model.TypeAuthType, nil, err)
+ identifierImpl := a.getIdentifierTypeImpl(identifierJSON, nil, nil)
+ if identifierImpl == nil {
+ return nil, nil, errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, nil)
}
+ identifier := identifierImpl.getIdentifier()
var updatedAccount *model.Account
var params map[string]interface{}
transaction := func(context storage.TransactionContext) error {
//1. check if the user exists
- account, err := a.storage.FindAccount(context, appOrg.ID, authType.ID, identifier)
+ account, err := a.storage.FindAccount(context, appOrg.ID, identifierImpl.getCode(), identifier)
if err != nil {
return errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
}
if account == nil {
- return errors.ErrorData(logutils.StatusMissing, model.TypeAccount, &logutils.FieldArgs{"app_org_id": appOrg.ID, "auth_type": authType.Code, "identifier": identifier})
- }
-
- //2. check if the user's auth type is verified
- accountAuthType := account.GetAccountAuthType(authType.ID, identifier)
- if accountAuthType == nil || accountAuthType.Unverified {
- return errors.ErrorData("Unverified", model.TypeAccountAuthType, &logutils.FieldArgs{"app_org_id": appOrg.ID, "auth_type": authType.Code, "identifier": identifier}).SetStatus(utils.ErrorStatusUnverified)
+ return errors.ErrorData(logutils.StatusMissing, model.TypeAccount, &logutils.FieldArgs{"app_org_id": appOrg.ID, "auth_type": supportedAuthType.AuthType.Code, "identifier": identifier})
}
- //3. update account permissions
+ //2. update account permissions
updatedAccount = account
updated := false
revoked := false
@@ -699,7 +792,7 @@ func (a *Auth) UpdateAdminAccount(authenticationType string, appID string, orgID
updated = true
}
- //4. update account roles
+ //3. update account roles
added, removed, unchanged = utils.StringListDiff(roleIDs, account.GetAssignedRoleIDs())
if len(added) > 0 || len(removed) > 0 {
newRoles := []model.AppOrgRole{}
@@ -737,7 +830,7 @@ func (a *Auth) UpdateAdminAccount(authenticationType string, appID string, orgID
updated = true
}
- //5. update account groups
+ //4. update account groups
added, removed, unchanged = utils.StringListDiff(groupIDs, account.GetAssignedGroupIDs())
if len(added) > 0 || len(removed) > 0 {
newGroups := []model.AppOrgGroup{}
@@ -775,7 +868,7 @@ func (a *Auth) UpdateAdminAccount(authenticationType string, appID string, orgID
updated = true
}
- //6. update account scopes
+ //5. update account scopes
if scopes != nil && utils.Contains(updaterPermissions, model.UpdateScopesPermission) && !utils.DeepEqual(account.Scopes, scopes) {
for i, scope := range scopes {
parsedScope, err := authorization.ScopeFromString(scope)
@@ -796,7 +889,7 @@ func (a *Auth) UpdateAdminAccount(authenticationType string, appID string, orgID
updated = true
}
- //7. delete active login sessions if anything was revoked
+ //6. delete active login sessions if anything was revoked
if revoked {
err = a.storage.DeleteLoginSessionsByIdentifier(context, account.ID)
if err != nil {
@@ -824,7 +917,7 @@ func (a *Auth) UpdateAdminAccount(authenticationType string, appID string, orgID
func (a *Auth) CreateAnonymousAccount(context storage.TransactionContext, appID string, orgID string, anonymousID string, preferences map[string]interface{},
systemConfigs map[string]interface{}, skipExistsCheck bool, l *logs.Log) (*model.Account, error) {
// check if the provided auth type is supported by the provided application and organization
- authType, appOrg, err := a.validateAuthTypeForAppOrg(AuthTypeAnonymous, appID, orgID)
+ supportedAuthType, _, appOrg, err := a.validateAuthType(AuthTypeAnonymous, nil, &appID, orgID)
if err != nil || appOrg == nil {
return nil, errors.WrapErrorAction(logutils.ActionValidate, model.TypeAuthType, nil, err)
}
@@ -839,7 +932,7 @@ func (a *Auth) CreateAnonymousAccount(context storage.TransactionContext, appID
return errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
}
if account != nil {
- return errors.ErrorData(logutils.StatusFound, model.TypeAccount, &logutils.FieldArgs{"app_org_id": appOrg.ID, "auth_type": authType.Code, "account_id": anonymousID})
+ return errors.ErrorData(logutils.StatusFound, model.TypeAccount, &logutils.FieldArgs{"app_org_id": appOrg.ID, "auth_type": supportedAuthType.AuthType.Code, "account_id": anonymousID})
}
}
@@ -863,43 +956,92 @@ func (a *Auth) CreateAnonymousAccount(context storage.TransactionContext, appID
return newAccount, nil
}
-// VerifyCredential verifies credential (checks the verification code in the credentials collection)
-func (a *Auth) VerifyCredential(id string, verification string, l *logs.Log) error {
- credential, err := a.storage.FindCredential(nil, id)
- if err != nil || credential == nil {
- return errors.WrapErrorAction(logutils.ActionFind, model.TypeCredential, nil, err)
+// VerifyIdentifier verifies credential (checks the verification code in the credentials collection)
+func (a *Auth) VerifyIdentifier(id string, verification string, l *logs.Log) (*model.AccountIdentifier, error) {
+ //get the auth type
+ account, err := a.storage.FindAccountByIdentifierID(nil, id)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
}
+ if account == nil {
+ return nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccount, &logutils.FieldArgs{"identifiers.id": id})
+ }
+ a.setLogContext(account, l)
- if credential.Verified {
- return errors.ErrorAction(logutils.ActionVerify, model.TypeCredential, logutils.StringArgs("already verified"))
+ accountIdentifier := account.GetAccountIdentifierByID(id)
+ if accountIdentifier == nil {
+ return nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccountIdentifier, &logutils.FieldArgs{"id": id})
+ }
+ if accountIdentifier.Verified {
+ return accountIdentifier, nil
}
- //get the auth type
- authType, err := a.storage.FindAuthType(credential.AuthType.ID)
- if err != nil || authType == nil {
- return errors.WrapErrorAction(logutils.ActionLoadCache, model.TypeAuthType, logutils.StringArgs(credential.AuthType.ID), err)
+ identifierImpl := a.getIdentifierTypeImpl("", &accountIdentifier.Code, &accountIdentifier.Identifier)
+ if identifierImpl == nil {
+ return nil, errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, &logutils.FieldArgs{"code": accountIdentifier.Code, "identifier": accountIdentifier.Identifier})
}
- if !authType.UseCredentials {
- return errors.ErrorData(logutils.StatusInvalid, model.TypeAuthType, logutils.StringArgs("credential verification"))
+
+ if identifierChannel, ok := identifierImpl.(authCommunicationChannel); ok {
+ err = identifierChannel.verifyIdentifier(accountIdentifier, verification)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionValidate, "verification code", nil, err)
+ }
+ } else {
+ return nil, errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, logutils.StringArgs(accountIdentifier.Code))
}
- authImpl, err := a.getAuthTypeImpl(*authType)
+ return accountIdentifier, a.updateAccountIdentifier(nil, account, accountIdentifier)
+}
+
+// SendVerifyIdentifier sends the verification code to the identifier
+func (a *Auth) SendVerifyIdentifier(appTypeIdentifier string, orgID string, apiKey string, identifierJSON string, l *logs.Log) error {
+ //validate if the provided auth type is supported by the provided application and organization
+ _, appOrg, err := a.validateAppOrg(&appTypeIdentifier, nil, orgID)
if err != nil {
- return errors.WrapErrorAction(logutils.ActionLoadCache, model.TypeAuthType, nil, err)
+ return errors.WrapErrorAction(logutils.ActionValidate, model.TypeApplicationOrganization, nil, err)
}
- authTypeCreds, err := authImpl.verifyCredential(credential, verification, l)
- if err != nil || authTypeCreds == nil {
- return errors.WrapErrorAction(logutils.ActionValidate, "verification code", nil, err)
+ //validate api key before making db calls
+ err = a.validateAPIKey(apiKey, appOrg.Application.ID)
+ if err != nil {
+ return errors.WrapErrorData(logutils.StatusInvalid, model.TypeAPIKey, nil, err)
}
- credential.Verified = true
- credential.Value = authTypeCreds
- if err = a.storage.UpdateCredential(nil, credential); err != nil {
- return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeCredential, nil, err)
+ identifierImpl := a.getIdentifierTypeImpl(identifierJSON, nil, nil)
+ if identifierImpl == nil {
+ return errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, nil)
}
- return nil
+ code := identifierImpl.getCode()
+ identifier := identifierImpl.getIdentifier()
+
+ account, err := a.storage.FindAccount(nil, appOrg.ID, code, identifier)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
+ }
+ if account == nil {
+ return errors.ErrorData(logutils.StatusMissing, model.TypeAccount, nil)
+ }
+ a.setLogContext(account, l)
+
+ accountIdentifier := account.GetAccountIdentifier(code, identifier)
+ if accountIdentifier == nil {
+ return errors.WrapErrorAction(logutils.ActionFind, model.TypeAccountIdentifier, &logutils.FieldArgs{"identifier": identifier}, err)
+ }
+ if accountIdentifier.Verified {
+ return errors.ErrorData(logutils.StatusInvalid, "identifier verification status", &logutils.FieldArgs{"verified": true})
+ }
+
+ if identifierChannel, ok := identifierImpl.(authCommunicationChannel); ok {
+ _, err = identifierChannel.sendVerifyIdentifier(accountIdentifier, appOrg.Application.Name)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionSend, "verification code", nil, err)
+ }
+ } else {
+ return errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, logutils.StringArgs(accountIdentifier.Code))
+ }
+
+ return a.updateAccountIdentifier(nil, account, accountIdentifier)
}
// UpdateCredential updates the credential object with the new value
@@ -924,29 +1066,27 @@ func (a *Auth) UpdateCredential(accountID string, accountAuthTypeID string, para
return errors.WrapErrorAction(logutils.ActionFind, model.TypeAuthType, nil, err)
}
if accountAuthType.Credential == nil {
- return errors.ErrorData(logutils.StatusMissing, model.TypeCredential, logutils.StringArgs("reset password"))
+ return errors.ErrorData(logutils.StatusMissing, model.TypeCredential, nil)
}
- credential := accountAuthType.Credential
//Determine the auth type for resetPassword
- authType := accountAuthType.AuthType
- if !authType.UseCredentials {
- return errors.ErrorData(logutils.StatusInvalid, model.TypeAuthType, logutils.StringArgs("reset password"))
+ if !accountAuthType.SupportedAuthType.AuthType.UseCredentials {
+ return errors.ErrorData(logutils.StatusInvalid, model.TypeAuthType, nil)
}
- authImpl, err := a.getAuthTypeImpl(authType)
+ authImpl, err := a.getAuthTypeImpl(accountAuthType.SupportedAuthType)
if err != nil {
return errors.WrapErrorAction(logutils.ActionLoadCache, model.TypeAuthType, nil, err)
}
- authTypeCreds, err := authImpl.resetCredential(credential, nil, params, l)
+ authTypeCreds, err := authImpl.resetCredential(accountAuthType.Credential, nil, params)
if err != nil || authTypeCreds == nil {
- return errors.WrapErrorAction(logutils.ActionValidate, "reset password", nil, err)
+ return errors.WrapErrorAction(logutils.ActionValidate, "reset credential", nil, err)
}
//Update the credential with new password
- credential.Value = authTypeCreds
- if err = a.storage.UpdateCredential(nil, credential); err != nil {
+ accountAuthType.Credential.Value = authTypeCreds
+ if err = a.storage.UpdateCredentialValue(accountAuthType.Credential.ID, accountAuthType.Credential.Value); err != nil {
return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeCredential, nil, err)
}
@@ -970,12 +1110,21 @@ func (a *Auth) ResetForgotCredential(credsID string, resetCode string, params st
return errors.WrapErrorAction(logutils.ActionFind, model.TypeCredential, nil, err)
}
- //Determine the auth type for resetPassword
- authType, err := a.storage.FindAuthType(credential.AuthType.ID)
+ //get account by the credential ID (this is valid for now because there are no credentials shared between app orgs)
+ account, err := a.storage.FindAccountByCredentialID(nil, credsID)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, &logutils.FieldArgs{"credential_id": credsID}, err)
+ }
+ if account == nil {
+ return errors.ErrorData(logutils.StatusMissing, model.TypeAccount, &logutils.FieldArgs{"credential_id": credsID})
+ }
+
+ //validate if the provided auth type is supported by the provided application and organization
+ authType, _, _, err := a.validateAuthType(credential.AuthType.ID, nil, &account.AppOrg.Application.ID, account.AppOrg.Organization.ID)
if err != nil || authType == nil {
- return errors.WrapErrorAction(logutils.ActionLoadCache, model.TypeAuthType, logutils.StringArgs(credential.AuthType.ID), err)
+ return errors.WrapErrorAction(logutils.ActionValidate, model.TypeAuthType, nil, err)
}
- if !authType.UseCredentials {
+ if !authType.AuthType.UseCredentials {
return errors.ErrorData(logutils.StatusInvalid, model.TypeAuthType, logutils.StringArgs("reset forgot credential"))
}
@@ -984,13 +1133,13 @@ func (a *Auth) ResetForgotCredential(credsID string, resetCode string, params st
return errors.WrapErrorAction(logutils.ActionLoadCache, model.TypeAuthType, nil, err)
}
- authTypeCreds, err := authImpl.resetCredential(credential, &resetCode, params, l)
+ authTypeCreds, err := authImpl.resetCredential(credential, &resetCode, params)
if err != nil || authTypeCreds == nil {
return errors.WrapErrorAction(logutils.ActionValidate, model.TypeCredential, nil, err)
}
//Update the credential with new password
credential.Value = authTypeCreds
- if err = a.storage.UpdateCredential(nil, credential); err != nil {
+ if err = a.storage.UpdateCredentialValue(credential.ID, credential.Value); err != nil {
return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeCredential, nil, err)
}
@@ -1000,16 +1149,17 @@ func (a *Auth) ResetForgotCredential(credsID string, resetCode string, params st
// ForgotCredential initiate forgot credential process (generates a reset link and sends to the given identifier for email auth type)
//
// Input:
-// authenticationType (string): Name of the authentication method for provided creds (eg. "email", "username", "illinois_oidc")
-// identifier: identifier of the account auth type
+// authenticationType (string): Name of the authentication method for provided creds (eg. "password")
+// identifierJSON (string): JSON string of the user's identifier and the identifier code
// appTypeIdentifier (string): Identifier of the app type/client that the user is logging in from
// orgID (string): ID of the organization that the user is logging in
// apiKey (string): API key to validate the specified app
+// userIdentifier (*string): Optional user identifier for backwards compatibility
// Returns:
// error: if any
-func (a *Auth) ForgotCredential(authenticationType string, appTypeIdentifier string, orgID string, apiKey string, identifier string, l *logs.Log) error {
+func (a *Auth) ForgotCredential(authenticationType string, identifierJSON string, appTypeIdentifier string, orgID string, apiKey string, l *logs.Log) error {
//validate if the provided auth type is supported by the provided application and organization
- authType, _, appOrg, err := a.validateAuthType(authenticationType, appTypeIdentifier, orgID)
+ authType, _, appOrg, err := a.validateAuthType(authenticationType, &appTypeIdentifier, nil, orgID)
if err != nil || authType == nil || appOrg == nil {
return errors.WrapErrorAction(logutils.ActionValidate, model.TypeAuthType, nil, err)
}
@@ -1027,95 +1177,67 @@ func (a *Auth) ForgotCredential(authenticationType string, appTypeIdentifier str
}
//check if the auth types uses credentials
- if !authType.UseCredentials {
+ if !authType.AuthType.UseCredentials {
return errors.ErrorData(logutils.StatusInvalid, model.TypeAuthType, logutils.StringArgs("credential reset"))
}
- authImpl, err := a.getAuthTypeImpl(*authType)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionLoadCache, model.TypeAuthType, nil, err)
+ identifierImpl := a.getIdentifierTypeImpl(identifierJSON, nil, nil)
+ if identifierImpl == nil {
+ return errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, nil)
}
- authTypeID := authType.ID
+
+ code := identifierImpl.getCode()
+ identifier := identifierImpl.getIdentifier()
//Find the credential for setting reset code and expiry and sending credID in reset link
- account, err := a.storage.FindAccount(nil, appOrg.ID, authTypeID, identifier)
+ account, err := a.storage.FindAccount(nil, appOrg.ID, code, identifier)
if err != nil {
return errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
}
-
- accountAuthType, err := a.findAccountAuthType(account, authType, identifier)
- if accountAuthType == nil {
- return errors.WrapErrorAction(logutils.ActionFind, model.TypeAccountAuthType, nil, err)
- }
- credential := accountAuthType.Credential
- if credential == nil {
- return errors.ErrorData(logutils.StatusMissing, model.TypeCredential, logutils.StringArgs("credential reset"))
+ if account == nil {
+ return errors.ErrorData(logutils.StatusMissing, model.TypeAccount, &logutils.FieldArgs{"app_org_id": appOrg.ID, "identifier": identifier})
}
a.setLogContext(account, l)
- //do not allow to reset credential for unverified credentials
- err = a.checkCredentialVerified(authImpl, accountAuthType, l)
- if err != nil {
- return err
+ accountIdentifier := account.GetAccountIdentifier(code, identifier)
+ if accountIdentifier == nil {
+ return errors.ErrorData(logutils.StatusMissing, model.TypeAccountIdentifier, &logutils.FieldArgs{"identifier": identifier})
}
- authTypeCreds, err := authImpl.forgotCredential(credential, identifier, appOrg.Application.Name, l)
- if err != nil || authTypeCreds == nil {
- return errors.WrapErrorAction(logutils.ActionValidate, "forgot password", nil, err)
+ accountAuthTypes, err := a.findAccountAuthTypesAndCredentials(account, *authType)
+ if len(accountAuthTypes) == 0 {
+ return errors.ErrorData(logutils.StatusMissing, model.TypeAccountAuthType, &logutils.FieldArgs{"auth_type": authType.AuthType.Code, "identifier": identifier})
}
-
- //Update the credential with reset code and expiry
- credential.Value = authTypeCreds
- if err = a.storage.UpdateCredential(nil, credential); err != nil {
- return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeCredential, nil, err)
+ if len(accountAuthTypes) > 1 {
+ return errors.ErrorData(logutils.StatusInvalid, model.TypeAccountAuthType, &logutils.FieldArgs{"auth_type": authType.AuthType.Code, "identifier": identifier, "count": len(accountAuthTypes)})
}
- return nil
-}
-// SendVerifyCredential sends the verification code to the identifier
-func (a *Auth) SendVerifyCredential(authenticationType string, appTypeIdentifier string, orgID string, apiKey string, identifier string, l *logs.Log) error {
- //validate if the provided auth type is supported by the provided application and organization
- authType, _, appOrg, err := a.validateAuthType(authenticationType, appTypeIdentifier, orgID)
- if err != nil || authType == nil || appOrg == nil {
- return errors.WrapErrorAction(logutils.ActionValidate, model.TypeAuthType, nil, err)
- }
- //validate api key before making db calls
- err = a.validateAPIKey(apiKey, appOrg.Application.ID)
- if err != nil {
- return errors.WrapErrorData(logutils.StatusInvalid, model.TypeAPIKey, nil, err)
- }
-
- if !authType.UseCredentials {
- return errors.ErrorData(logutils.StatusInvalid, model.TypeAuthType, logutils.StringArgs("credential verification code"))
+ credential := accountAuthTypes[0].Credential
+ if credential == nil {
+ return errors.ErrorData(logutils.StatusMissing, model.TypeCredential, logutils.StringArgs("credential reset"))
}
+ a.setLogContext(account, l)
authImpl, err := a.getAuthTypeImpl(*authType)
if err != nil {
return errors.WrapErrorAction(logutils.ActionLoadCache, model.TypeAuthType, nil, err)
}
- account, err := a.storage.FindAccount(nil, appOrg.ID, authType.ID, identifier)
+
+ err = identifierImpl.checkVerified(accountIdentifier, appOrg.Application.Name)
if err != nil {
- return errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
- }
- accountAuthType, err := a.findAccountAuthType(account, authType, identifier)
- if accountAuthType == nil {
- return errors.WrapErrorAction(logutils.ActionFind, model.TypeAccountAuthType, nil, err)
- }
- a.setLogContext(account, l)
- credential := accountAuthType.Credential
- if credential == nil {
- return errors.ErrorData(logutils.StatusMissing, model.TypeCredential, logutils.StringArgs("credential verification code"))
+ return err
}
- if credential.Verified {
- return errors.ErrorData(logutils.StatusInvalid, "credential verification status", &logutils.FieldArgs{"verified": true})
+ authTypeCreds, err := authImpl.forgotCredential(identifierImpl, credential, *appOrg)
+ if err != nil || authTypeCreds == nil {
+ return errors.WrapErrorAction(logutils.ActionValidate, "forgot password", nil, err)
}
- err = authImpl.sendVerifyCredential(credential, appOrg.Application.Name, l)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionSend, "verification code", nil, err)
+ //Update the credential with reset code and expiry
+ credential.Value = authTypeCreds
+ if err = a.storage.UpdateCredentialValue(credential.ID, credential.Value); err != nil {
+ return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeCredential, nil, err)
}
-
return nil
}
@@ -1668,7 +1790,7 @@ func (a *Auth) GetAdminToken(claims tokenauth.Claims, appID string, orgID string
return "", errors.ErrorData(logutils.StatusMissing, model.TypeApplicationOrganization, &logutils.FieldArgs{"org_id": orgID, "app_id": appID})
}
- adminClaims := a.getStandardClaims(claims.Subject, claims.UID, claims.Name, claims.Email, claims.Phone, claims.Audience, orgID, appID, claims.AuthType,
+ adminClaims := a.getStandardClaims(claims.Subject, claims.Name, claims.Email, claims.Phone, claims.Username, claims.Audience, orgID, appID, claims.AuthType,
claims.ExternalIDs, &claims.ExpiresAt, false, false, true, claims.System, claims.Service, claims.FirstParty, claims.SessionID)
return a.buildAccessToken(adminClaims, claims.Permissions, claims.Scope)
}
@@ -1678,8 +1800,8 @@ func (a *Auth) GetAdminToken(claims tokenauth.Claims, appID string, orgID string
//
// Input:
// accountID (string): ID of the account to link the creds to
-// authenticationType (string): Name of the authentication method for provided creds (eg. "email", "username", "illinois_oidc")
-// appTypeIdentifier (string): identifier of the app type/client that the user is logging in from
+// authenticationType (string): Name of the authentication method for provided creds (eg. "password", "webauthn", "illinois_oidc")
+// appTypeIdentifier (string): Identifier of the app type/client that the user is logging in from
// creds (string): Credentials/JSON encoded credential structure defined for the specified auth type
// params (string): JSON encoded params defined by specified auth type
// l (*logs.Log): Log object pointer for request
@@ -1687,7 +1809,7 @@ func (a *Auth) GetAdminToken(claims tokenauth.Claims, appID string, orgID string
// message (*string): response message
// account (*model.Account): account data after the operation
func (a *Auth) LinkAccountAuthType(accountID string, authenticationType string, appTypeIdentifier string, creds string, params string, l *logs.Log) (*string, *model.Account, error) {
- message := ""
+ var message *string
var newAccountAuthType *model.AccountAuthType
account, err := a.storage.FindAccountByID(nil, accountID)
@@ -1695,26 +1817,32 @@ func (a *Auth) LinkAccountAuthType(accountID string, authenticationType string,
return nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
}
if account == nil {
- return nil, nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccount, &logutils.FieldArgs{"id": accountID})
+ return nil, nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccount, &logutils.FieldArgs{"id": accountID}).SetStatus(utils.ErrorStatusNotFound)
}
//validate if the provided auth type is supported by the provided application and organization
- authType, appType, appOrg, err := a.validateAuthType(authenticationType, appTypeIdentifier, account.AppOrg.Organization.ID)
+ authType, appType, appOrg, err := a.validateAuthType(authenticationType, &appTypeIdentifier, nil, account.AppOrg.Organization.ID)
if err != nil || authType == nil || appType == nil || appOrg == nil {
- return nil, nil, errors.WrapErrorAction(logutils.ActionValidate, model.TypeAuthType, nil, err)
+ return nil, nil, errors.WrapErrorAction(logutils.ActionValidate, model.TypeAuthType, nil, err).SetStatus(utils.ErrorStatusNotAllowed)
}
- if authType.IsAnonymous {
+ if authType.AuthType.IsAnonymous {
return nil, nil, errors.ErrorData(logutils.StatusInvalid, model.TypeAuthType, &logutils.FieldArgs{"anonymous": true})
- } else if authType.IsExternal {
- newAccountAuthType, err = a.linkAccountAuthTypeExternal(*account, *authType, *appType, *appOrg, creds, params, l)
+ } else if authType.AuthType.IsExternal {
+ // only one account auth type per each external auth type is allowed
+ externalAats := account.GetAccountAuthTypes(authType.AuthType.Code)
+ if len(externalAats) > 0 {
+ return nil, nil, errors.ErrorData(logutils.StatusFound, model.TypeAuthType, &logutils.FieldArgs{"allow_multiple": false, "code": authType.AuthType.Code})
+ }
+
+ newAccountAuthType, err = a.linkAccountAuthTypeExternal(account, *authType, *appType, *appOrg, creds, params, l)
if err != nil {
- return nil, nil, errors.WrapErrorAction("linking", model.TypeCredential, nil, err)
+ return nil, nil, errors.WrapErrorAction("linking", model.TypeAccountAuthType, nil, err)
}
} else {
- message, newAccountAuthType, err = a.linkAccountAuthType(*account, *authType, *appOrg, creds, params, l)
+ message, newAccountAuthType, err = a.linkAccountAuthType(account, *authType, *appOrg, appType, creds, params)
if err != nil {
- return nil, nil, errors.WrapErrorAction("linking", model.TypeCredential, nil, err)
+ return nil, nil, errors.WrapErrorAction("linking", model.TypeAccountAuthType, nil, err)
}
}
@@ -1722,7 +1850,7 @@ func (a *Auth) LinkAccountAuthType(accountID string, authenticationType string,
account.AuthTypes = append(account.AuthTypes, *newAccountAuthType)
}
- return &message, account, nil
+ return message, account, nil
}
// UnlinkAccountAuthType unlinks credentials from an existing account.
@@ -1730,14 +1858,56 @@ func (a *Auth) LinkAccountAuthType(accountID string, authenticationType string,
//
// Input:
// accountID (string): ID of the account to unlink creds from
-// authenticationType (string): Name of the authentication method of account auth type to unlink
-// appTypeIdentifier (string): Identifier of the app type/client that the user is logging in from
-// identifier (string): Identifier of account auth type to unlink
+// accountAuthTypeID (*string): Account auth type to unlink
+// authenticationType (*string): Name of the authentication method of account auth type to unlink
+// identifier (*string): Identifier to unlink
// l (*logs.Log): Log object pointer for request
// Returns:
// account (*model.Account): account data after the operation
-func (a *Auth) UnlinkAccountAuthType(accountID string, authenticationType string, appTypeIdentifier string, identifier string, l *logs.Log) (*model.Account, error) {
- return a.unlinkAccountAuthType(accountID, authenticationType, appTypeIdentifier, identifier, l)
+func (a *Auth) UnlinkAccountAuthType(accountID string, accountAuthTypeID *string, authenticationType *string, identifier *string, admin bool, l *logs.Log) (*model.Account, error) {
+ return a.unlinkAccountAuthType(accountID, accountAuthTypeID, authenticationType, identifier, admin)
+}
+
+// LinkAccountIdentifier links an identifier to an existing account.
+func (a *Auth) LinkAccountIdentifier(accountID string, identifierJSON string, admin bool, l *logs.Log) (*string, *model.Account, error) {
+ identifierImpl := a.getIdentifierTypeImpl(identifierJSON, nil, nil)
+ if identifierImpl == nil {
+ return nil, nil, errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, nil)
+ }
+
+ if identifierImpl.getCode() == IdentifierTypeExternal && !admin {
+ return nil, nil, errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, logutils.StringArgs(IdentifierTypeExternal)).SetStatus(utils.ErrorStatusNotAllowed)
+ }
+
+ account, err := a.storage.FindAccountByID(nil, accountID)
+ if err != nil {
+ return nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
+ }
+ if account == nil {
+ return nil, nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccount, nil).SetStatus(utils.ErrorStatusNotFound)
+ }
+
+ message, err := a.linkAccountIdentifier(nil, account, identifierImpl)
+ if err != nil {
+ return nil, nil, errors.WrapErrorAction("linking", model.TypeAccountIdentifier, nil, err)
+ }
+
+ return message, account, nil
+}
+
+// UnlinkAccountIdentifier unlinks an identifier from an existing account.
+func (a *Auth) UnlinkAccountIdentifier(accountID string, accountIdentifierID string, admin bool, l *logs.Log) (*model.Account, error) {
+ account, err := a.storage.FindAccountByID(nil, accountID)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
+ }
+ if account == nil {
+ return nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccount, nil)
+ }
+
+ err = a.unlinkAccountIdentifier(nil, account, &accountIdentifierID, nil, admin)
+
+ return account, nil
}
// DeleteAccount deletes an account for the given id
@@ -1766,23 +1936,30 @@ func (a *Auth) DeleteAccount(id string) error {
// InitializeSystemAccount initializes the first system account
func (a *Auth) InitializeSystemAccount(context storage.TransactionContext, authType model.AuthType, appOrg model.ApplicationOrganization,
allSystemPermission string, email string, password string, clientVersion string, l *logs.Log) (string, error) {
- //auth type
- authImpl, err := a.getAuthTypeImpl(authType)
+ now := time.Now().UTC()
+ profile := model.Profile{ID: uuid.NewString(), DateCreated: now}
+ privacy := model.Privacy{Public: false}
+ permissions := []string{allSystemPermission}
+
+ credsMap := passwordCreds{Password: password}
+ credsBytes, err := json.Marshal(credsMap)
if err != nil {
- return "", errors.WrapErrorAction(logutils.ActionLoadCache, model.TypeAuthType, nil, err)
+ return "", errors.WrapErrorAction(logutils.ActionMarshal, typePasswordCreds, nil, err)
}
+ creds := string(credsBytes)
- now := time.Now()
- profile := model.Profile{ID: uuid.NewString(), Email: email, DateCreated: now}
- privacy := model.Privacy{Public: false}
- permissions := []string{allSystemPermission}
+ code := IdentifierTypeEmail
+ identifierImpl := a.getIdentifierTypeImpl("", &code, &email)
+ if identifierImpl == nil {
+ return "", errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, &logutils.FieldArgs{"code": code, "identifier": email})
+ }
- _, accountAuthType, err := a.applySignUpAdmin(context, authImpl, nil, authType, appOrg, email, password, profile, privacy, "", permissions, nil, nil, nil, permissions, &clientVersion, l)
+ _, account, err := a.signUpNewAccount(context, identifierImpl, model.SupportedAuthType{AuthType: authType}, appOrg, nil, creds, "", &clientVersion, profile, privacy, nil, permissions, nil, nil, nil, permissions, l)
if err != nil {
return "", errors.WrapErrorAction(logutils.ActionRegister, "initial system user", &logutils.FieldArgs{"email": email}, err)
}
- return accountAuthType.Account.ID, nil
+ return account.ID, nil
}
// GrantAccountPermissions grants new permissions to an account after validating the assigner has required permissions
diff --git a/core/auth/auth.go b/core/auth/auth.go
index 73f19a837..ad7426cfe 100644
--- a/core/auth/auth.go
+++ b/core/auth/auth.go
@@ -16,6 +16,7 @@ package auth
import (
"core-building-block/core/model"
+ "core-building-block/driven/phoneverifier"
"core-building-block/driven/storage"
"core-building-block/utils"
"encoding/json"
@@ -57,13 +58,19 @@ const (
// UpdateScopesPermission is the permission that allows an admin to update account/role scopes
UpdateScopesPermission string = "update_auth_scopes"
+ defaultIllinoisOIDCIdentifier string = "uin"
+ illinoisOIDCCode string = "illinois_oidc"
+
typeMail logutils.MessageDataType = "mail"
+ typeIdentifierType logutils.MessageDataType = "identifier type"
typeExternalAuthType logutils.MessageDataType = "external auth type"
typeAnonymousAuthType logutils.MessageDataType = "anonymous auth type"
typeServiceAuthType logutils.MessageDataType = "service auth type"
typeAuth logutils.MessageDataType = "auth"
typeAuthRefreshParams logutils.MessageDataType = "auth refresh params"
+ typeVerificationCode string = "verification code"
+
refreshTokenLength int = 256
sessionDeletePeriod int = 24 // hours
@@ -82,11 +89,13 @@ const (
// Auth represents the auth functionality unit
type Auth struct {
- storage Storage
- emailer Emailer
+ storage Storage
+ emailer Emailer
+ phoneVerifier PhoneVerifier
logger *logs.Logger
+ identifierTypes map[string]identifierType
authTypes map[string]authType
externalAuthTypes map[string]externalAuthType
anonymousAuthTypes map[string]anonymousAuthType
@@ -120,9 +129,8 @@ type Auth struct {
}
// NewAuth creates a new auth instance
-func NewAuth(serviceID string, host string, authPrivKey *keys.PrivKey, authService *authservice.AuthService, storage Storage, emailer Emailer, minTokenExp *int64,
- maxTokenExp *int64, supportLegacySigs bool, twilioAccountSID string, twilioToken string, twilioServiceSID string, profileBB ProfileBuildingBlock,
- smtpHost string, smtpPortNum int, smtpUser string, smtpPassword string, smtpFrom string, logger *logs.Logger, version string) (*Auth, error) {
+func NewAuth(serviceID string, host string, authPrivKey *keys.PrivKey, authService *authservice.AuthService, storage Storage, emailer Emailer, phoneVerifier PhoneVerifier,
+ profileBB ProfileBuildingBlock, minTokenExp *int64, maxTokenExp *int64, supportLegacySigs bool, version string, logger *logs.Logger) (*Auth, error) {
if minTokenExp == nil {
var minTokenExpVal int64 = 5
minTokenExp = &minTokenExpVal
@@ -133,6 +141,7 @@ func NewAuth(serviceID string, host string, authPrivKey *keys.PrivKey, authServi
maxTokenExp = &maxTokenExpVal
}
+ identifierTypes := map[string]identifierType{}
authTypes := map[string]authType{}
externalAuthTypes := map[string]externalAuthType{}
anonymousAuthTypes := map[string]anonymousAuthType{}
@@ -147,10 +156,11 @@ func NewAuth(serviceID string, host string, authPrivKey *keys.PrivKey, authServi
deleteSessionsTimerDone := make(chan bool)
- auth := &Auth{storage: storage, emailer: emailer, logger: logger, authTypes: authTypes, externalAuthTypes: externalAuthTypes, anonymousAuthTypes: anonymousAuthTypes,
- serviceAuthTypes: serviceAuthTypes, mfaTypes: mfaTypes, authPrivKey: authPrivKey, ServiceRegManager: nil, serviceID: serviceID, host: host, minTokenExp: *minTokenExp,
- maxTokenExp: *maxTokenExp, profileBB: profileBB, cachedIdentityProviders: cachedIdentityProviders, identityProvidersLock: identityProvidersLock,
- apiKeys: apiKeys, apiKeysLock: apiKeysLock, deleteSessionsTimerDone: deleteSessionsTimerDone, version: version}
+ auth := &Auth{storage: storage, emailer: emailer, phoneVerifier: phoneVerifier, logger: logger, identifierTypes: identifierTypes, authTypes: authTypes,
+ externalAuthTypes: externalAuthTypes, anonymousAuthTypes: anonymousAuthTypes, serviceAuthTypes: serviceAuthTypes, mfaTypes: mfaTypes, authPrivKey: authPrivKey,
+ ServiceRegManager: nil, serviceID: serviceID, host: host, minTokenExp: *minTokenExp, maxTokenExp: *maxTokenExp, profileBB: profileBB,
+ cachedIdentityProviders: cachedIdentityProviders, identityProvidersLock: identityProvidersLock, apiKeys: apiKeys, apiKeysLock: apiKeysLock,
+ deleteSessionsTimerDone: deleteSessionsTimerDone, version: version}
err := auth.storeCoreRegs()
if err != nil {
@@ -175,13 +185,19 @@ func NewAuth(serviceID string, host string, authPrivKey *keys.PrivKey, authServi
auth.SignatureAuth = signatureAuth
+ // identifier types
+ initUsernameIdentifier(auth)
+ initEmailIdentifier(auth)
+ initPhoneIdentifier(auth)
+ initExternalIdentifier(auth)
+
// auth types
- initUsernameAuth(auth)
- initEmailAuth(auth)
- initPhoneAuth(auth, twilioAccountSID, twilioToken, twilioServiceSID)
- initFirebaseAuth(auth)
initAnonymousAuth(auth)
- initSignatureAuth(auth)
+ initPasswordAuth(auth)
+ initCodeAuth(auth)
+ initWebAuthnAuth(auth)
+ // initFirebaseAuth(auth)
+ // initSignatureAuth(auth)
// external auth types
initOidcAuth(auth)
@@ -216,99 +232,102 @@ func (a *Auth) SetIdentityBB(identityBB IdentityBuildingBlock) {
a.identityBB = identityBB
}
-func (a *Auth) applyExternalAuthType(authType model.AuthType, appType model.ApplicationType, appOrg model.ApplicationOrganization, creds string, params string, clientVersion *string,
- regProfile model.Profile, privacy model.Privacy, regPreferences map[string]interface{}, username string, admin bool, l *logs.Log) (*model.AccountAuthType, map[string]interface{}, []model.MFAType, map[string]string, error) {
- var accountAuthType *model.AccountAuthType
+func (a *Auth) applyExternalAuthType(supportedAuthType model.SupportedAuthType, appType model.ApplicationType, appOrg model.ApplicationOrganization, creds string, params string, clientVersion *string,
+ regProfile model.Profile, privacy model.Privacy, regPreferences map[string]interface{}, admin bool, l *logs.Log) (map[string]interface{}, *model.Account, []model.MFAType, error) {
+ var newAccount *model.Account
var mfaTypes []model.MFAType
- var externalIDs map[string]string
//external auth type
- authImpl, err := a.getExternalAuthTypeImpl(authType)
+ authImpl, err := a.getExternalAuthTypeImpl(supportedAuthType.AuthType)
if err != nil {
- return nil, nil, nil, nil, errors.WrapErrorAction(logutils.ActionLoadCache, typeExternalAuthType, nil, err)
+ return nil, nil, nil, errors.WrapErrorAction(logutils.ActionLoadCache, typeExternalAuthType, nil, err)
}
//1. get the user from the external system
//var externalUser *model.ExternalSystemUser
- externalUser, extParams, externalCreds, err := authImpl.externalLogin(authType, appType, appOrg, creds, params, l)
+ externalUser, extParams, externalCreds, err := authImpl.externalLogin(supportedAuthType.AuthType, appType, appOrg, creds, params, l)
if err != nil {
- return nil, nil, nil, nil, errors.WrapErrorAction("logging in", "external user", nil, err)
+ return nil, nil, nil, errors.WrapErrorAction("logging in", "external user", nil, err)
}
//2. check if the user exists
- account, err := a.storage.FindAccount(nil, appOrg.ID, authType.ID, externalUser.Identifier)
+ // get the correct code for the external identifier from the external IDs map
+ code := ""
+ for k, v := range externalUser.ExternalIDs {
+ if v == externalUser.Identifier {
+ code = k
+ }
+ }
+ if code == "" && externalUser.Email == externalUser.Identifier {
+ code = IdentifierTypeEmail
+ }
+
+ account, err := a.storage.FindAccount(nil, appOrg.ID, code, externalUser.Identifier)
if err != nil {
- return nil, nil, nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
+ return nil, nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
}
a.setLogContext(account, l)
- canSignIn := a.canSignIn(account, authType.ID, externalUser.Identifier)
+ canSignIn := a.canSignIn(account, code, externalUser.Identifier)
if canSignIn {
//account exists
- accountAuthType, err = a.applySignInExternal(account, authType, appOrg, *externalUser, externalCreds, l)
+ newAccount, err = a.applySignInExternal(account, supportedAuthType, appOrg, *externalUser, externalCreds, l)
if err != nil {
- return nil, nil, nil, nil, errors.WrapErrorAction(logutils.ActionApply, "external sign in", nil, err)
+ return nil, nil, nil, errors.WrapErrorAction(logutils.ActionApply, "external sign in", nil, err)
}
mfaTypes = account.GetVerifiedMFATypes()
- externalIDs = account.ExternalIDs
} else if !admin {
//user does not exist, we need to register it
- accountAuthType, err = a.applySignUpExternal(nil, authType, appOrg, *externalUser, externalCreds, regProfile, privacy, regPreferences, username, clientVersion, l)
+ newAccount, err = a.applySignUpExternal(nil, supportedAuthType, appOrg, *externalUser, externalCreds, regProfile, privacy, regPreferences, clientVersion, l)
if err != nil {
- return nil, nil, nil, nil, errors.WrapErrorAction(logutils.ActionApply, "external sign up", nil, err)
+ return nil, nil, nil, errors.WrapErrorAction(logutils.ActionApply, "external sign up", nil, err)
}
- externalIDs = externalUser.ExternalIDs
} else {
- return nil, nil, nil, nil, errors.ErrorData(logutils.StatusInvalid, "sign up", &logutils.FieldArgs{"identifier": externalUser.Identifier, "auth_type": authType.Code, "app_org_id": appOrg.ID, "admin": true}).SetStatus(utils.ErrorStatusNotAllowed)
+ return nil, nil, nil, errors.ErrorData(logutils.StatusInvalid, "sign up", &logutils.FieldArgs{"identifier": externalUser.Identifier, "auth_type": supportedAuthType.AuthType.Code, "app_org_id": appOrg.ID, "admin": true}).SetStatus(utils.ErrorStatusNotAllowed)
}
//TODO: make sure we do not return any refresh tokens in extParams
- return accountAuthType, extParams, mfaTypes, externalIDs, nil
+ return extParams, newAccount, mfaTypes, nil
}
-func (a *Auth) applySignInExternal(account *model.Account, authType model.AuthType, appOrg model.ApplicationOrganization,
- externalUser model.ExternalSystemUser, externalCreds string, l *logs.Log) (*model.AccountAuthType, error) {
- var accountAuthType *model.AccountAuthType
+func (a *Auth) applySignInExternal(account *model.Account, supportedAuthType model.SupportedAuthType, appOrg model.ApplicationOrganization,
+ externalUser model.ExternalSystemUser, externalCreds string, l *logs.Log) (*model.Account, error) {
+ var accountAuthTypes []model.AccountAuthType
var err error
- //find account auth type
- accountAuthType, err = a.findAccountAuthType(account, &authType, externalUser.Identifier)
+ //find account auth type (there should only be one account auth type with matching auth type code)
+ accountAuthTypes, err = a.findAccountAuthTypesAndCredentials(account, supportedAuthType)
if err != nil {
return nil, err
}
+ if len(accountAuthTypes) != 1 {
+ return nil, errors.ErrorData(logutils.StatusInvalid, model.TypeAccountAuthType,
+ &logutils.FieldArgs{"count": len(accountAuthTypes), "auth_type_id": supportedAuthType.AuthType.ID, "identifier": externalUser.Identifier})
+ }
//check if need to update the account data
- newAccount, err := a.updateExternalUserIfNeeded(*accountAuthType, externalUser, authType, appOrg, externalCreds, l)
+ newAccount, err := a.updateExternalUserIfNeeded(accountAuthTypes[0], externalUser, supportedAuthType.AuthType, appOrg, externalCreds, l)
if err != nil {
return nil, errors.WrapErrorAction(logutils.ActionUpdate, model.TypeExternalSystemUser, nil, err)
}
if newAccount != nil {
- accountAuthType.Account = *newAccount
+ newAccount.SortAccountAuthTypes(accountAuthTypes[0].ID, "")
+ newAccount.SortAccountIdentifiers(externalUser.Identifier)
}
- if accountAuthType.Unverified {
- accountAuthType.SetUnverified(false)
- err := a.storage.UpdateAccountAuthType(*accountAuthType)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionUpdate, model.TypeAccountAuthType, nil, err)
- }
- }
-
- return accountAuthType, nil
+ return newAccount, nil
}
-func (a *Auth) applySignUpExternal(context storage.TransactionContext, authType model.AuthType, appOrg model.ApplicationOrganization, externalUser model.ExternalSystemUser,
- externalCreds string, regProfile model.Profile, privacy model.Privacy, regPreferences map[string]interface{}, username string, clientVersion *string, l *logs.Log) (*model.AccountAuthType, error) {
- var accountAuthType *model.AccountAuthType
-
+func (a *Auth) applySignUpExternal(context storage.TransactionContext, supportedAuthType model.SupportedAuthType, appOrg model.ApplicationOrganization, externalUser model.ExternalSystemUser,
+ externalCreds string, regProfile model.Profile, privacy model.Privacy, regPreferences map[string]interface{}, clientVersion *string, l *logs.Log) (*model.Account, error) {
//1. prepare external admin user data
- identifier, aatParams, useSharedProfile, profile, preferences, err := a.prepareExternalUserData(authType, appOrg, externalUser, regProfile, nil, l)
+ identifiers, aatParams, profile, preferences, err := a.prepareExternalUserData(supportedAuthType.AuthType, appOrg, externalUser, regProfile, nil, l)
if err != nil {
return nil, errors.WrapErrorAction(logutils.ActionPrepare, "external admin user data", nil, err)
}
- identityProviderID, ok := authType.Params["identity_provider"].(string)
+ identityProviderID, ok := supportedAuthType.AuthType.Params["identity_provider"].(string)
if !ok {
return nil, errors.ErrorData(logutils.StatusMissing, "identity provider id", nil)
}
@@ -340,89 +359,90 @@ func (a *Auth) applySignUpExternal(context storage.TransactionContext, authType
l.WarnError(logutils.MessageActionError(logutils.ActionGet, "external authorization", nil), err)
}
- //4. check username
- if username != "" {
- err = a.checkUsername(nil, &appOrg, username)
- if err != nil {
- return nil, err
- }
- }
-
- //5. register the account
- accountAuthType, err = a.registerUser(context, authType, identifier, aatParams, appOrg, nil, useSharedProfile,
- externalUser.ExternalIDs, *profile, privacy, preferences, username, nil, externalRoles, externalGroups, nil, nil, clientVersion, l)
+ //4. register the account
+ //External and anonymous auth is automatically verified, otherwise verified if credential has been verified previously
+ account, err := a.registerUser(context, identifiers, supportedAuthType.AuthType, true, aatParams, appOrg, nil, externalUser.ExternalIDs,
+ *profile, privacy, preferences, nil, externalRoles, externalGroups, nil, nil, clientVersion, l)
if err != nil {
return nil, errors.WrapErrorAction(logutils.ActionRegister, model.TypeAccount, nil, err)
}
- return accountAuthType, nil
+ return account, nil
}
-func (a *Auth) applySignUpAdminExternal(context storage.TransactionContext, authType model.AuthType, appOrg model.ApplicationOrganization, externalUser model.ExternalSystemUser, regProfile model.Profile,
- privacy model.Privacy, username string, permissions []string, roleIDs []string, groupIDs []string, scopes []string, creatorPermissions []string, clientVersion *string, l *logs.Log) (*model.AccountAuthType, error) {
- var accountAuthType *model.AccountAuthType
-
+func (a *Auth) applySignUpAdminExternal(context storage.TransactionContext, supportedAuthType model.SupportedAuthType, appOrg model.ApplicationOrganization, externalUser model.ExternalSystemUser, regProfile model.Profile,
+ privacy model.Privacy, permissions []string, roleIDs []string, groupIDs []string, scopes []string, creatorPermissions []string, clientVersion *string, l *logs.Log) (*model.Account, error) {
//1. prepare external admin user data
- identifier, aatParams, useSharedProfile, profile, _, err := a.prepareExternalUserData(authType, appOrg, externalUser, regProfile, nil, l)
+ identifiers, aatParams, profile, _, err := a.prepareExternalUserData(supportedAuthType.AuthType, appOrg, externalUser, regProfile, nil, l)
if err != nil {
return nil, errors.WrapErrorAction(logutils.ActionPrepare, "external admin user data", nil, err)
}
- //2. check username
- if username != "" {
- err = a.checkUsername(nil, &appOrg, username)
- if err != nil {
- return nil, err
- }
- }
-
- //3. register the account
- accountAuthType, err = a.registerUser(context, authType, identifier, aatParams, appOrg, nil, useSharedProfile, nil, *profile, privacy, nil,
- username, permissions, roleIDs, groupIDs, scopes, creatorPermissions, clientVersion, l)
+ //2. register the account
+ //External and anonymous auth is automatically verified, otherwise verified if credential has been verified previously
+ account, err := a.registerUser(context, identifiers, supportedAuthType.AuthType, false, aatParams, appOrg, nil, nil, *profile, privacy, nil,
+ permissions, roleIDs, groupIDs, scopes, creatorPermissions, clientVersion, l)
if err != nil {
return nil, errors.WrapErrorAction(logutils.ActionRegister, "admin account", nil, err)
}
- return accountAuthType, nil
+ return account, nil
}
func (a *Auth) prepareExternalUserData(authType model.AuthType, appOrg model.ApplicationOrganization, externalUser model.ExternalSystemUser, regProfile model.Profile,
- regPreferences map[string]interface{}, l *logs.Log) (string, map[string]interface{}, bool, *model.Profile, map[string]interface{}, error) {
+ regPreferences map[string]interface{}, l *logs.Log) ([]model.AccountIdentifier, map[string]interface{}, *model.Profile, map[string]interface{}, error) {
var profile model.Profile
var preferences map[string]interface{}
- //1. check if needs to use shared profile
- useSharedProfile, sharedProfile, _, err := a.applySharedProfile(appOrg.Application, authType.ID, externalUser.Identifier, l)
- if err != nil {
- return "", nil, false, nil, nil, errors.WrapErrorAction(logutils.ActionApply, "shared profile", nil, err)
- }
+ /*
+ //1. check if needs to use shared profile
+ useSharedProfile, sharedProfile, _, err := a.applySharedProfile(appOrg, externalUser.Identifier, l)
+ if err != nil {
+ return nil, nil, false, nil, nil, errors.WrapErrorAction(logutils.ActionApply, "shared profile", nil, err)
+ }
- if useSharedProfile {
- l.Infof("%s uses a shared profile", externalUser.Identifier)
+ if useSharedProfile {
+ l.Infof("%s uses a shared profile", externalUser.Identifier)
+
+ //merge client profile and shared profile
+ profile = a.mergeProfiles(regProfile, sharedProfile, true)
+ preferences = regPreferences
+ }
- //merge client profile and shared profile
- profile = a.mergeProfiles(regProfile, sharedProfile, true)
- preferences = regPreferences
- } else {
l.Infof("%s does not use a shared profile", externalUser.Identifier)
+ */
- profile = regProfile
- preferences = regPreferences
+ profile = regProfile
+ preferences = regPreferences
- //prepare profile and preferences
- preparedProfile, preparedPreferences, err := a.prepareRegistrationData(authType, externalUser.Identifier, profile, preferences, l)
- if err != nil {
- return "", nil, false, nil, nil, errors.WrapErrorAction(logutils.ActionPrepare, "user registration data", nil, err)
- }
- profile = *preparedProfile
- preferences = preparedPreferences
+ //prepare profile and preferences
+ preparedProfile, preparedPreferences, err := a.prepareRegistrationData(authType, externalUser.Identifier, profile, preferences, l)
+ if err != nil {
+ return nil, nil, nil, nil, errors.WrapErrorAction(logutils.ActionPrepare, "user registration data", nil, err)
}
+ profile = *preparedProfile
+ preferences = preparedPreferences
//2. prepare the registration data
- params := map[string]interface{}{}
- params["user"] = externalUser
+ params := map[string]interface{}{"user": externalUser}
- return externalUser.Identifier, params, useSharedProfile, &profile, preferences, nil
+ //3. create the account identifiers
+ now := time.Now().UTC()
+ accountID := uuid.NewString()
+ accountIdentifiers := make([]model.AccountIdentifier, 0)
+ for k, v := range externalUser.ExternalIDs {
+ primary := (v == externalUser.Identifier)
+ accountIdentifiers = append(accountIdentifiers, model.AccountIdentifier{ID: uuid.NewString(), Code: k, Identifier: v, Verified: true,
+ Sensitive: utils.Contains(externalUser.SensitiveExternalIDs, k), Primary: &primary, Account: model.Account{ID: accountID}, DateCreated: now})
+ }
+ if externalUser.Email != "" {
+ primary := (externalUser.Email == externalUser.Identifier)
+ accountIdentifiers = append(accountIdentifiers, model.AccountIdentifier{ID: uuid.NewString(), Code: IdentifierTypeEmail, Identifier: externalUser.Email,
+ Verified: externalUser.IsEmailVerified, Sensitive: true, Primary: &primary, Account: model.Account{ID: accountID}, DateCreated: now})
+ }
+ // AccountAuthTypeID field will be set later
+
+ return accountIdentifiers, params, &profile, preferences, nil
}
func (a *Auth) applyProfileDataFromExternalUser(profile model.Profile, newExternalUser model.ExternalSystemUser,
@@ -441,14 +461,10 @@ func (a *Auth) applyProfileDataFromExternalUser(profile model.Profile, newExtern
if len(newExternalUser.LastName) > 0 && (alwaysSync || len(profile.LastName) == 0 || (currentExternalUser != nil && currentExternalUser.LastName != newExternalUser.LastName)) {
newProfile.LastName = newExternalUser.LastName
}
- //email
- if len(newExternalUser.Email) > 0 && (alwaysSync || len(profile.Email) == 0 || (currentExternalUser != nil && currentExternalUser.Email != newExternalUser.Email)) {
- newProfile.Email = newExternalUser.Email
- }
changed := !utils.DeepEqual(profile, newProfile)
if changed {
- now := time.Now()
+ now := time.Now().UTC()
newProfile.DateUpdated = &now
return &newProfile, nil
}
@@ -461,15 +477,9 @@ func (a *Auth) updateExternalUserIfNeeded(accountAuthType model.AccountAuthType,
l.Info("updateExternalUserIfNeeded")
//get the current external user
- currentDataMap := accountAuthType.Params["user"]
- currentDataJSON, err := utils.ConvertToJSON(currentDataMap)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionMarshal, model.TypeExternalSystemUser, nil, err)
- }
- var currentData *model.ExternalSystemUser
- err = json.Unmarshal(currentDataJSON, ¤tData)
+ currentData, err := utils.JSONConvert[model.ExternalSystemUser, interface{}](accountAuthType.Params["user"])
if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, model.TypeExternalSystemUser, nil, err)
+ return nil, errors.WrapErrorAction(logutils.ActionParse, model.TypeExternalSystemUser, nil, err)
}
identityProviderID, ok := authType.Params["identity_provider"].(string)
@@ -493,11 +503,13 @@ func (a *Auth) updateExternalUserIfNeeded(accountAuthType model.AccountAuthType,
var newAccount *model.Account
//there is changes so we need to update it
//TODO: Can we do this all in a single storage operation?
+ updatedActiveStatus := !accountAuthType.Active
updatedExternalUser := !currentData.Equals(externalUser)
accountAuthType.Params["user"] = externalUser
- now := time.Now()
+ now := time.Now().UTC()
accountAuthType.DateUpdated = &now
+ //TODO: make sure external identifiers get updated in storage and in account memory
transaction := func(context storage.TransactionContext) error {
//1. first find the account record
account, err := a.storage.FindAccountByAuthTypeID(context, accountAuthType.ID)
@@ -512,6 +524,7 @@ func (a *Auth) updateExternalUserIfNeeded(accountAuthType model.AccountAuthType,
newAccountAuthTypes := make([]model.AccountAuthType, len(account.AuthTypes))
for j, aAuthType := range account.AuthTypes {
if aAuthType.ID == accountAuthType.ID {
+ accountAuthType.Active = true
newAccountAuthTypes[j] = accountAuthType
} else {
newAccountAuthTypes[j] = aAuthType
@@ -520,15 +533,7 @@ func (a *Auth) updateExternalUserIfNeeded(accountAuthType model.AccountAuthType,
account.AuthTypes = newAccountAuthTypes
// 3. update external ids
- for k, v := range externalUser.ExternalIDs {
- if account.ExternalIDs == nil {
- account.ExternalIDs = make(map[string]string)
- }
- if account.ExternalIDs[k] != v {
- updatedExternalUser = true
- account.ExternalIDs[k] = v
- }
- }
+ updatedIdentifiers := a.updateExternalIdentifiers(account, accountAuthType.ID, &externalUser, false)
// 4. update profile
profileUpdated := false
@@ -557,7 +562,7 @@ func (a *Auth) updateExternalUserIfNeeded(accountAuthType model.AccountAuthType,
}
// 6. update account if needed
- if updatedExternalUser || profileUpdated || rolesUpdated || groupsUpdated {
+ if updatedActiveStatus || updatedExternalUser || updatedIdentifiers || profileUpdated || rolesUpdated || groupsUpdated {
err = a.storage.SaveAccount(context, account)
if err != nil {
return errors.WrapErrorAction(logutils.ActionSave, model.TypeAccount, nil, err)
@@ -598,175 +603,219 @@ func (a *Auth) applyAnonymousAuthType(authType model.AuthType, creds string) (st
return anonymousID, account, anonymousParams, nil
}
-func (a *Auth) applyAuthType(authType model.AuthType, appOrg model.ApplicationOrganization, creds string, params string, clientVersion *string, regProfile model.Profile,
- privacy model.Privacy, regPreferences map[string]interface{}, username string, admin bool, l *logs.Log) (string, *model.AccountAuthType, []model.MFAType, map[string]string, error) {
+func (a *Auth) applyAuthType(supportedAuthType model.SupportedAuthType, appOrg model.ApplicationOrganization, appType *model.ApplicationType,
+ creds string, params string, clientVersion *string, regProfile model.Profile, privacy model.Privacy, regPreferences map[string]interface{},
+ accountIdentifierID *string, admin bool, l *logs.Log) (map[string]interface{}, *model.Account, []model.MFAType, error) {
- //auth type
- authImpl, err := a.getAuthTypeImpl(authType)
+ //identifier type
+ identifierImpl := a.getIdentifierTypeImpl(creds, nil, nil)
+ authImpl, err := a.getAuthTypeImpl(supportedAuthType)
if err != nil {
- return "", nil, nil, nil, errors.WrapErrorAction(logutils.ActionLoadCache, model.TypeAuthType, nil, err)
+ return nil, nil, nil, errors.WrapErrorAction(logutils.ActionLoadCache, model.TypeAuthType, nil, err)
}
- //check if the user exists check
- userIdentifier, err := authImpl.getUserIdentifier(creds)
- if err != nil {
- return "", nil, nil, nil, errors.WrapErrorAction(logutils.ActionGet, "user identifier", nil, err)
- }
+ var account *model.Account
+ if identifierImpl == nil {
+ if accountIdentifierID != nil {
+ account, err = a.storage.FindAccountByIdentifierID(nil, *accountIdentifierID)
+ if err != nil {
+ return nil, nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, &logutils.FieldArgs{"identifier.id": *accountIdentifierID}, err)
+ }
+ if account == nil {
+ return nil, nil, nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccount, &logutils.FieldArgs{"identifier.id": *accountIdentifierID})
+ }
- if userIdentifier != "" {
- if authType.Code == AuthTypeTwilioPhone && regProfile.Phone == "" {
- regProfile.Phone = userIdentifier
- } else if authType.Code == AuthTypeEmail && regProfile.Email == "" {
- regProfile.Email = userIdentifier
- } else if authType.Code == authTypeUsername {
- username = userIdentifier
+ // attempt sign-in after finding the account
+ retParams, verifiedMFATypes, err := a.applySignIn(nil, authImpl, supportedAuthType, appOrg, account, creds, params, accountIdentifierID, l)
+ return retParams, account, verifiedMFATypes, err
}
+
+ // attempt identifier-less login (only sign in is allowed because sign up is impossible without a user identifier)
+ message, credID, err := a.checkCredentials(nil, authImpl, nil, nil, creds, params, appOrg)
+ if err != nil {
+ return nil, nil, nil, errors.WrapErrorAction(logutils.ActionVerify, model.TypeCredential, nil, err)
+ }
+
+ if message != nil {
+ return map[string]interface{}{"message": *message}, nil, nil, nil
+ }
+
+ account, err := a.storage.FindAccountByCredentialID(nil, credID)
+ if err != nil {
+ return nil, nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, &logutils.FieldArgs{"credential_id": credID}, err)
+ }
+
+ if authImpl.requireIdentifierVerificationForSignIn() && len(account.GetVerifiedAccountIdentifiers()) == 0 {
+ return nil, nil, nil, errors.ErrorData(logutils.StatusInvalid, model.TypeAccount, &logutils.FieldArgs{"verified": false})
+ }
+
+ accountAuthTypes, err := a.findAccountAuthTypesAndCredentials(account, supportedAuthType)
+ if err != nil {
+ return nil, nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccountAuthType, nil, err)
+ }
+
+ _, verifiedMFATypes, err := a.completeSignIn(nil, account, accountAuthTypes, supportedAuthType, credID)
+ return nil, account, verifiedMFATypes, err
}
- account, err := a.storage.FindAccount(nil, appOrg.ID, authType.ID, userIdentifier)
+ code := identifierImpl.getCode()
+ identifier := identifierImpl.getIdentifier()
+ account, err = a.storage.FindAccount(nil, appOrg.ID, code, identifier)
if err != nil {
- return "", nil, nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err) //TODO add args..
+ return nil, nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, &logutils.FieldArgs{"app_org_id": appOrg.ID, "code": code, "identifier": identifier}, err)
}
a.setLogContext(account, l)
- canSignIn := a.canSignIn(account, authType.ID, userIdentifier)
+ canSignIn := a.canSignIn(account, code, identifier)
//check if it is sign in or sign up
isSignUp, err := a.isSignUp(canSignIn, params, l)
if err != nil {
- return "", nil, nil, nil, errors.WrapErrorAction(logutils.ActionVerify, "is sign up", nil, err)
+ return nil, nil, nil, errors.WrapErrorAction(logutils.ActionVerify, "is sign up", nil, err)
}
if isSignUp {
if admin {
- return "", nil, nil, nil, errors.ErrorData(logutils.StatusInvalid, "sign up", &logutils.FieldArgs{"identifier": userIdentifier,
- "auth_type": authType.Code, "app_org_id": appOrg.ID, "admin": true}).SetStatus(utils.ErrorStatusNotAllowed)
+ return nil, nil, nil, errors.ErrorData(logutils.StatusInvalid, "sign up", &logutils.FieldArgs{"identifier": identifier,
+ "auth_type": supportedAuthType.AuthType.Code, "app_org_id": appOrg.ID, "admin": true}).SetStatus(utils.ErrorStatusNotAllowed)
}
- message, accountAuthType, err := a.applySignUp(authImpl, account, authType, appOrg, userIdentifier, creds, params, clientVersion,
- regProfile, privacy, regPreferences, username, l)
+ retParams, account, err := a.applySignUp(identifierImpl, account, supportedAuthType, appOrg, appType, creds, params, clientVersion, regProfile, privacy, regPreferences, l)
if err != nil {
- return "", nil, nil, nil, err
+ return nil, nil, nil, err
}
- return message, accountAuthType, nil, nil, nil
+ return retParams, account, nil, nil
}
///apply sign in
- return a.applySignIn(authImpl, authType, account, userIdentifier, creds, l)
+ retParams, verifiedMFATypes, err := a.applySignIn(identifierImpl, authImpl, supportedAuthType, appOrg, account, creds, params, nil, l)
+ return retParams, account, verifiedMFATypes, err
}
-func (a *Auth) applySignIn(authImpl authType, authType model.AuthType, account *model.Account, userIdentifier string,
- creds string, l *logs.Log) (string, *model.AccountAuthType, []model.MFAType, map[string]string, error) {
+func (a *Auth) applySignIn(identifierImpl identifierType, authImpl authType, supportedAuthType model.SupportedAuthType, appOrg model.ApplicationOrganization,
+ account *model.Account, creds string, params string, accountIdentifierID *string, l *logs.Log) (map[string]interface{}, []model.MFAType, error) {
if account == nil {
- return "", nil, nil, nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccount, nil).SetStatus(utils.ErrorStatusNotFound)
+ return nil, nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccount, nil).SetStatus(utils.ErrorStatusNotFound)
}
- //find account auth type
- accountAuthType, err := a.findAccountAuthType(account, &authType, userIdentifier)
- if accountAuthType == nil {
- return "", nil, nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccountAuthType, nil, err)
+ //find account identifier
+ var accountIdentifier *model.AccountIdentifier
+ identifier := ""
+ if accountIdentifierID != nil {
+ accountIdentifier = account.GetAccountIdentifierByID(*accountIdentifierID)
+ } else if identifierImpl != nil {
+ identifier = identifierImpl.getIdentifier()
+ accountIdentifier = account.GetAccountIdentifier(identifierImpl.getCode(), identifier)
+ }
+ if accountIdentifier == nil {
+ return nil, nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccountIdentifier, &logutils.FieldArgs{"identifier": identifier})
+ }
+ if !accountIdentifier.Verified && accountIdentifier.Linked {
+ return nil, nil, errors.ErrorData(logutils.StatusInvalid, model.TypeAccountIdentifier, &logutils.FieldArgs{"verified": false, "linked": true})
}
- if accountAuthType.Unverified && accountAuthType.Linked {
- return "", nil, nil, nil, errors.ErrorData(logutils.StatusInvalid, model.TypeAccountAuthType, &logutils.FieldArgs{"verified": false, "linked": true})
+ if identifierImpl == nil {
+ identifierImpl = a.getIdentifierTypeImpl("", &accountIdentifier.Code, &accountIdentifier.Identifier)
+ if identifierImpl == nil {
+ return nil, nil, errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, &logutils.FieldArgs{"code": accountIdentifier.Code, "identifier": accountIdentifier.Identifier})
+ }
+ }
+ if identifierImpl.requireVerificationForSignIn() || authImpl.requireIdentifierVerificationForSignIn() {
+ err := identifierImpl.checkVerified(accountIdentifier, appOrg.Application.Name)
+ if err != nil {
+ return nil, nil, errors.WrapErrorData(logutils.StatusInvalid, model.TypeAccountIdentifier, &logutils.FieldArgs{"verified": false}, err)
+ }
}
- var message string
- message, err = a.checkCredentials(authImpl, authType, accountAuthType, creds, l)
+ //find account auth type
+ accountAuthTypes, err := a.findAccountAuthTypesAndCredentials(account, supportedAuthType)
if err != nil {
- return "", nil, nil, nil, errors.WrapErrorAction(logutils.ActionVerify, model.TypeCredential, nil, err)
+ return nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccountAuthType, nil, err)
}
- return message, accountAuthType, account.GetVerifiedMFATypes(), account.ExternalIDs, nil
-}
-
-func (a *Auth) checkCredentialVerified(authImpl authType, accountAuthType *model.AccountAuthType, l *logs.Log) error {
- verified, expired, err := authImpl.isCredentialVerified(accountAuthType.Credential, l)
+ updateIdentifier := !accountIdentifier.Verified
+ message, credID, err := a.checkCredentials(identifierImpl, authImpl, &account.ID, accountAuthTypes, creds, params, appOrg)
if err != nil {
- return errors.WrapErrorAction(logutils.ActionVerify, "credential verified", nil, err)
+ return nil, nil, errors.WrapErrorAction(logutils.ActionVerify, model.TypeCredential, nil, err)
}
- if !*verified {
- //it is unverified
- if expired == nil || !*expired {
- //not expired, just notify the client that it is "unverified"
- return errors.ErrorData("unverified", "credential", nil).SetStatus(utils.ErrorStatusUnverified)
- }
- //expired, first restart the verification and then notify the client that it is unverified and verification is restarted
-
- //restart credential verification
- err = authImpl.restartCredentialVerification(accountAuthType.Credential, accountAuthType.Account.AppOrg.Application.Name, l)
+ account.SortAccountIdentifiers(identifierImpl.getIdentifier())
+ if updateIdentifier && message == nil {
+ accountIdentifier.Verified = true
+ err := a.storage.UpdateAccountIdentifier(nil, *accountIdentifier)
if err != nil {
- return errors.WrapErrorAction("restarting", "credential verification", nil, err)
+ return nil, nil, errors.WrapErrorAction(logutils.ActionUpdate, model.TypeAccountIdentifier, nil, err)
}
-
- //notify the client
- return errors.ErrorData("expired", "credential verification", nil).SetStatus(utils.ErrorStatusVerificationExpired)
}
- return nil
+ return a.completeSignIn(message, account, accountAuthTypes, supportedAuthType, credID)
}
-func (a *Auth) checkCredentials(authImpl authType, authType model.AuthType, accountAuthType *model.AccountAuthType, creds string, l *logs.Log) (string, error) {
- //check is verified
- if authType.UseCredentials {
- err := a.checkCredentialVerified(authImpl, accountAuthType, l)
- if err != nil {
- return "", err
+func (a *Auth) completeSignIn(message *string, account *model.Account, accountAuthTypes []model.AccountAuthType, supportedAuthType model.SupportedAuthType, credID string) (map[string]interface{}, []model.MFAType, error) {
+ //sort by the account auth type used to perform the login
+ for _, aat := range accountAuthTypes {
+ if credID == "" || (aat.Credential != nil && aat.Credential.ID == credID) {
+ account.SortAccountAuthTypes(aat.ID, "")
+
+ // if the account auth type is not already active, mark it as active
+ if !aat.Active {
+ aat.Active = true
+ err := a.storage.UpdateAccountAuthType(nil, aat)
+ if err != nil {
+ return nil, nil, errors.WrapErrorAction(logutils.ActionUpdate, model.TypeAccountAuthType, nil, err)
+ }
+ }
+
+ break
}
}
+ var retParams map[string]interface{}
+ if message != nil {
+ retParams = map[string]interface{}{"message": *message}
+ }
+ return retParams, account.GetVerifiedMFATypes(), nil
+}
+
+func (a *Auth) checkCredentials(identifierImpl identifierType, authImpl authType, accountID *string, aats []model.AccountAuthType, creds string,
+ params string, appOrg model.ApplicationOrganization) (*string, string, error) {
//check the credentials
- message, err := authImpl.checkCredentials(*accountAuthType, creds, l)
+ msg, credID, err := authImpl.checkCredentials(identifierImpl, accountID, aats, creds, params, appOrg)
if err != nil {
- return message, errors.WrapErrorAction(logutils.ActionValidate, model.TypeCredential, nil, err)
+ return nil, "", errors.WrapErrorAction(logutils.ActionValidate, model.TypeCredential, nil, err)
}
- //if sign in was completed successfully, set auth type to verified
- if message == "" && accountAuthType.Unverified {
- accountAuthType.SetUnverified(false)
- err := a.storage.UpdateAccountAuthType(*accountAuthType)
- if err != nil {
- return "", errors.WrapErrorAction(logutils.ActionUpdate, model.TypeAccountAuthType, nil, err)
- }
+ var message *string
+ if msg != "" {
+ message = &msg
}
-
- return message, nil
+ return message, credID, nil
}
-func (a *Auth) applySignUp(authImpl authType, account *model.Account, authType model.AuthType, appOrg model.ApplicationOrganization, userIdentifier string, creds string,
- params string, clientVersion *string, regProfile model.Profile, privacy model.Privacy, regPreferences map[string]interface{}, username string, l *logs.Log) (string, *model.AccountAuthType, error) {
+func (a *Auth) applySignUp(identifierImpl identifierType, account *model.Account, supportedAuthType model.SupportedAuthType, appOrg model.ApplicationOrganization,
+ appType *model.ApplicationType, creds string, params string, clientVersion *string, regProfile model.Profile, privacy model.Privacy,
+ regPreferences map[string]interface{}, l *logs.Log) (map[string]interface{}, *model.Account, error) {
if account != nil {
- err := a.handleAccountAuthTypeConflict(*account, authType.ID, userIdentifier, true)
+ err := a.handleAccountIdentifierConflict(*account, identifierImpl, true)
if err != nil {
- return "", nil, err
+ return nil, nil, err
}
}
- if username != "" {
- err := a.checkUsername(nil, &appOrg, username)
+ if identifierImpl.getCode() == IdentifierTypeUsername {
+ username := identifierImpl.getIdentifier()
+ accounts, err := a.storage.FindAccountsByUsername(nil, &appOrg, username)
if err != nil {
- return "", nil, err
+ return nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
+ }
+ if len(accounts) > 0 {
+ return nil, nil, errors.ErrorData(logutils.StatusInvalid, model.TypeAccountUsername, logutils.StringArgs(username+" taken")).SetStatus(utils.ErrorStatusUsernameTaken)
}
}
- retParams, accountAuthType, err := a.signUpNewAccount(nil, authImpl, authType, appOrg, userIdentifier, creds, params, clientVersion, regProfile, privacy, regPreferences, username, nil, nil, nil, nil, nil, l)
+ retParams, account, err := a.signUpNewAccount(nil, identifierImpl, supportedAuthType, appOrg, appType, creds, params, clientVersion, regProfile, privacy, regPreferences, nil, nil, nil, nil, nil, l)
if err != nil {
- return "", nil, err
+ return nil, nil, err
}
- message, _ := retParams["message"].(string)
- return message, accountAuthType, nil
-}
-
-func (a *Auth) applySignUpAdmin(context storage.TransactionContext, authImpl authType, account *model.Account, authType model.AuthType, appOrg model.ApplicationOrganization, identifier string, password string,
- regProfile model.Profile, privacy model.Privacy, username string, permissions []string, roles []string, groups []string, scopes []string, creatorPermissions []string, clientVersion *string, l *logs.Log) (map[string]interface{}, *model.AccountAuthType, error) {
-
- if username != "" {
- err := a.checkUsername(nil, &appOrg, username)
- if err != nil {
- return nil, nil, err
- }
- }
-
- return a.signUpNewAccount(context, authImpl, authType, appOrg, identifier, password, "", clientVersion, regProfile, privacy, nil, username, permissions, roles, groups, scopes, creatorPermissions, l)
+ return retParams, account, nil
}
func (a *Auth) applyCreateAnonymousAccount(context storage.TransactionContext, appOrg model.ApplicationOrganization, anonymousID string,
@@ -775,161 +824,157 @@ func (a *Auth) applyCreateAnonymousAccount(context storage.TransactionContext, a
return a.storage.InsertAccount(context, account)
}
-func (a *Auth) signUpNewAccount(context storage.TransactionContext, authImpl authType, authType model.AuthType, appOrg model.ApplicationOrganization, userIdentifier string,
- creds string, params string, clientVersion *string, regProfile model.Profile, privacy model.Privacy, regPreferences map[string]interface{}, username string, permissions []string,
- roles []string, groups []string, scopes []string, creatorPermissions []string, l *logs.Log) (map[string]interface{}, *model.AccountAuthType, error) {
+func (a *Auth) signUpNewAccount(context storage.TransactionContext, identifierImpl identifierType, supportedAuthType model.SupportedAuthType, appOrg model.ApplicationOrganization,
+ appType *model.ApplicationType, creds string, params string, clientVersion *string, regProfile model.Profile, privacy model.Privacy, regPreferences map[string]interface{},
+ permissions []string, roles []string, groups []string, scopes []string, creatorPermissions []string, l *logs.Log) (map[string]interface{}, *model.Account, error) {
var retParams map[string]interface{}
+ var accountIdentifier *model.AccountIdentifier
var credential *model.Credential
var profile model.Profile
var preferences map[string]interface{}
- //check if needs to use shared profile
- useSharedProfile, sharedProfile, sharedCredential, err := a.applySharedProfile(appOrg.Application, authType.ID, userIdentifier, l)
- if err != nil {
- return nil, nil, errors.WrapErrorAction(logutils.ActionApply, "shared profile", nil, err)
- }
+ /*
+ //check if needs to use shared profile
+ useSharedProfile, sharedProfile, sharedCredential, err := a.applySharedProfile(appOrg, userIdentifier, l)
+ if err != nil {
+ return nil, nil, errors.WrapErrorAction(logutils.ActionApply, "shared profile", nil, err)
+ }
- if useSharedProfile {
- l.Infof("%s uses a shared profile", userIdentifier)
+ if useSharedProfile {
+ l.Infof("%s uses a shared profile", userIdentifier)
- //allow sign up only if the shared credential is verified
- if credential != nil && !credential.Verified {
- l.Infof("trying to sign up in %s with unverified shared credentials", appOrg.Organization.Name)
- return nil, nil, errors.ErrorData("unverified", model.TypeCredential, nil).SetStatus(utils.ErrorStatusSharedCredentialUnverified)
- }
+ //allow sign up only if the shared credential is verified
+ if credential != nil && !credential.Verified {
+ l.Infof("trying to sign up in %s with unverified shared credentials", appOrg.Organization.Name)
+ return nil, nil, errors.ErrorData("unverified", model.TypeCredential, nil).SetStatus(utils.ErrorStatusSharedCredentialUnverified)
+ }
- //merge client profile and shared profile
- profile = a.mergeProfiles(regProfile, sharedProfile, true)
- preferences = regPreferences
+ //merge client profile and shared profile
+ profile = a.mergeProfiles(regProfile, sharedProfile, true)
+ preferences = regPreferences
+
+ credential = sharedCredential
+ retParams = map[string]interface{}{"message": "successfully registered"}
+ }
- credential = sharedCredential
- retParams = map[string]interface{}{"message": "successfuly registered"}
- } else {
l.Infof("%s does not use a shared profile", userIdentifier)
+ */
- profile = regProfile
- preferences = regPreferences
+ profile = regProfile
+ preferences = regPreferences
+
+ preparedProfile, preparedPreferences, err := a.prepareRegistrationData(supportedAuthType.AuthType, identifierImpl.getIdentifier(), profile, preferences, l)
+ if err != nil {
+ return nil, nil, errors.WrapErrorAction(logutils.ActionPrepare, "user registration data", nil, err)
+ }
+ profile = *preparedProfile
+ preferences = preparedPreferences
- preparedProfile, preparedPreferences, err := a.prepareRegistrationData(authType, userIdentifier, profile, preferences, l)
+ //apply sign up
+ authImpl, err := a.getAuthTypeImpl(supportedAuthType)
+ if err != nil {
+ return nil, nil, errors.WrapErrorAction(logutils.ActionLoadCache, model.TypeAuthType, nil, err)
+ }
+ if creatorPermissions == nil {
+ var message string
+ message, accountIdentifier, credential, err = authImpl.signUp(identifierImpl, nil, appOrg, creds, params)
if err != nil {
- return nil, nil, errors.WrapErrorAction(logutils.ActionPrepare, "user registration data", nil, err)
+ return nil, nil, errors.WrapErrorAction("signing up", "user", nil, err)
}
- profile = *preparedProfile
- preferences = preparedPreferences
-
- credID := uuid.NewString()
-
- //apply sign up
- var credentialValue map[string]interface{}
- if creatorPermissions == nil {
- var message string
- message, credentialValue, err = authImpl.signUp(authType, appOrg, creds, params, credID, l)
- if err != nil {
- return nil, nil, errors.WrapErrorAction("signing up", "user", nil, err)
- }
+ if message != "" {
retParams = map[string]interface{}{"message": message}
- } else {
- retParams, credentialValue, err = authImpl.signUpAdmin(authType, appOrg, userIdentifier, creds, credID)
- if err != nil {
- return nil, nil, errors.WrapErrorAction("signing up", "admin user", nil, err)
- }
}
-
- //credential
- if credentialValue != nil {
- now := time.Now()
- credential = &model.Credential{ID: credID, AccountsAuthTypes: nil, Value: credentialValue, Verified: false,
- AuthType: authType, DateCreated: now, DateUpdated: &now}
+ } else {
+ retParams, accountIdentifier, credential, err = authImpl.signUpAdmin(identifierImpl, appOrg, creds)
+ if err != nil {
+ return nil, nil, errors.WrapErrorAction("signing up", "admin user", nil, err)
}
}
+ if accountIdentifier == nil {
+ return nil, nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccountIdentifier, nil)
+ }
- accountAuthType, err := a.registerUser(context, authType, userIdentifier, nil, appOrg, credential, useSharedProfile,
- nil, profile, privacy, preferences, username, permissions, roles, groups, scopes, creatorPermissions, clientVersion, l)
+ if credential != nil {
+ credential.AuthType.ID = supportedAuthType.AuthType.ID
+ }
+
+ var accountAuthTypeParams map[string]interface{}
+ if supportedAuthType.AuthType.Code == AuthTypeWebAuthn && appType != nil {
+ accountAuthTypeParams = map[string]interface{}{"app_type_identifier": appType.Identifier}
+ }
+ account, err := a.registerUser(context, []model.AccountIdentifier{*accountIdentifier}, supportedAuthType.AuthType, retParams == nil, accountAuthTypeParams,
+ appOrg, credential, nil, profile, privacy, preferences, permissions, roles, groups, scopes, creatorPermissions, clientVersion, l)
if err != nil {
return nil, nil, errors.WrapErrorAction(logutils.ActionRegister, model.TypeAccount, nil, err)
}
- return retParams, accountAuthType, nil
+ return retParams, account, nil
}
-func (a *Auth) applySharedProfile(app model.Application, authTypeID string, userIdentifier string, l *logs.Log) (bool, *model.Profile, *model.Credential, error) {
- //do not share profiles by default
- useSharedProfile := false
-
- var sharedProfile *model.Profile
- var sharedCredential *model.Credential
-
- var err error
-
- //the application uses shared profiles
+/*
+func (a *Auth) applySharedProfile(app model.Application, userIdentifier string, l *logs.Log) (bool, *model.Profile, []model.Credential, error) {
if app.SharedIdentities {
+ //the application uses shared profiles
l.Infof("%s uses shared identities", app.Name)
- hasSharedProfile := false
- hasSharedProfile, sharedProfile, sharedCredential, err = a.hasSharedProfile(app, authTypeID, userIdentifier, l)
+ hasSharedProfile, sharedProfile, sharedCredential, err := a.hasSharedProfile(app, userIdentifier, l)
if err != nil {
return false, nil, nil, errors.WrapErrorAction(logutils.ActionVerify, "shared profile", nil, err)
}
if hasSharedProfile {
l.Infof("%s already has a profile, so use it", userIdentifier)
- useSharedProfile = true
} else {
l.Infof("%s does not have a profile", userIdentifier)
}
- } else {
- l.Infof("%s does not use shared identities", app.Name)
+ return hasSharedProfile, sharedProfile, sharedCredential, nil
}
- return useSharedProfile, sharedProfile, sharedCredential, nil
+ l.Infof("%s does not use shared identities", app.Name)
+ return false, nil, nil, nil
}
-func (a *Auth) hasSharedProfile(app model.Application, authTypeID string, userIdentifier string, l *logs.Log) (bool, *model.Profile, *model.Credential, error) {
+func (a *Auth) hasSharedProfile(app model.Application, userIdentifier string, l *logs.Log) (bool, *model.Profile, []model.Credential, error) {
l.Info("hasSharedProfile")
//find if already there is a profile for the application
- profiles, err := a.storage.FindAccountProfiles(app.ID, authTypeID, userIdentifier)
+ accounts, err := a.storage.FindAccountProfiles(app.ID, userIdentifier)
if err != nil {
return false, nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeProfile, nil, err)
}
- if len(profiles) == 0 {
+ //TODO: which account's profile to use?
+ //TODO: what if a different individual is using this identifier in another app org (e.g., username)? profile should not be shared
+ //TODO: may need to get profile ID from client (or entire copy of the shared profile on sign up)
+ if account == nil {
l.Info("there is no profile yet")
return false, nil, nil, nil
}
//find profile
- var profile *model.Profile
var credential *model.Credential
- var credentialID *string
- for _, current := range profiles {
- for _, account := range current.Accounts {
- for _, accAuthType := range account.AuthTypes {
- if accAuthType.Identifier == userIdentifier {
- //get the profile
- profile = ¤t
-
- if accAuthType.Credential != nil {
- credentialID = &accAuthType.Credential.ID //we have only the id loaded in the credential object
- }
- break
- }
- }
+ credentialIDs := make([]string, 0)
+ for _, accAuthType := range account.AuthTypes {
+ if accAuthType.Credential != nil {
+ // we have only the id loaded in the credential object
+ credentialIDs = append(credentialIDs, accAuthType.Credential.ID)
}
- }
-
- if profile == nil {
- return false, nil, nil, nil
+ break
}
//find the credential
- if credentialID != nil {
- credential, err = a.storage.FindCredential(nil, *credentialID)
+ credentials := make([]model.Credential, 0)
+ for _, credID := range credentialIDs {
+ credential, err = a.storage.FindCredential(nil, credID)
if err != nil {
return false, nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeCredential, nil, err)
}
+
+ credentials = append(credentials, *credential)
}
- return true, profile, credential, nil
+
+ return true, &account.Profile, credentials, nil
}
+*/
// validateAPIKey checks if the given API key is valid for the given app ID
func (a *Auth) validateAPIKey(apiKey string, appID string) error {
@@ -941,10 +986,10 @@ func (a *Auth) validateAPIKey(apiKey string, appID string) error {
return nil
}
-func (a *Auth) canSignIn(account *model.Account, authTypeID string, userIdentifier string) bool {
+func (a *Auth) canSignIn(account *model.Account, code string, identifier string) bool {
if account != nil {
- aat := account.GetAccountAuthType(authTypeID, userIdentifier)
- return aat == nil || !aat.Linked || !aat.Unverified
+ aat := account.GetAccountIdentifier(code, identifier)
+ return aat == nil || !aat.Linked || aat.Verified
}
return false
@@ -980,60 +1025,54 @@ func (a *Auth) isSignUp(accountExists bool, params string, l *logs.Log) (bool, e
return true, nil
}
-func (a *Auth) getAccount(authenticationType string, userIdentifier string, apiKey string, appTypeIdentifier string, orgID string) (*model.Account, string, error) {
- //validate if the provided auth type is supported by the provided application and organization
- authType, _, appOrg, err := a.validateAuthType(authenticationType, appTypeIdentifier, orgID)
+func (a *Auth) getAccount(code string, identifier string, apiKey string, appTypeIdentifier string, orgID string) (*model.Account, error) {
+ //validate if the provided app type is supported by the provided application and organization
+ _, appOrg, err := a.validateAppOrg(&appTypeIdentifier, nil, orgID)
if err != nil {
- return nil, "", errors.WrapErrorAction(logutils.ActionValidate, model.TypeAuthType, nil, err)
+ return nil, errors.WrapErrorAction(logutils.ActionValidate, model.TypeAuthType, nil, err)
}
//do not allow for admins
if appOrg.Application.Admin {
- return nil, "", errors.ErrorData(logutils.StatusInvalid, model.TypeApplication, logutils.StringArgs("not allowed for admins"))
+ return nil, errors.ErrorData(logutils.StatusInvalid, model.TypeApplication, logutils.StringArgs("not allowed for admins"))
}
//TODO: Ideally we would not make many database calls before validating the API key. Currently needed to get app ID
err = a.validateAPIKey(apiKey, appOrg.Application.ID)
if err != nil {
- return nil, "", errors.WrapErrorData(logutils.StatusInvalid, model.TypeAPIKey, nil, err)
+ return nil, errors.WrapErrorData(logutils.StatusInvalid, model.TypeAPIKey, nil, err)
}
//check if the account exists check
- account, err := a.storage.FindAccount(nil, appOrg.ID, authType.ID, userIdentifier)
+ account, err := a.storage.FindAccount(nil, appOrg.ID, code, identifier)
if err != nil {
- return nil, "", errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
+ return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, &logutils.FieldArgs{"app_org_id": appOrg.ID, "code": code, "identifier": identifier}, err)
}
- return account, authType.ID, nil
+ return account, nil
}
-func (a *Auth) findAccountAuthType(account *model.Account, authType *model.AuthType, identifier string) (*model.AccountAuthType, error) {
+func (a *Auth) findAccountAuthTypesAndCredentials(account *model.Account, supportedAuthType model.SupportedAuthType) ([]model.AccountAuthType, error) {
if account == nil {
return nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccount, nil)
}
- if authType == nil {
- return nil, errors.ErrorData(logutils.StatusMissing, model.TypeAuthType, nil)
- }
-
- accountAuthType := account.GetAccountAuthType(authType.ID, identifier)
- if accountAuthType == nil {
- return nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccountAuthType, nil)
- }
-
- accountAuthType.AuthType = *authType
+ accountAuthTypes := account.GetAccountAuthTypes(supportedAuthType.AuthType.Code)
+ for i, aat := range accountAuthTypes {
+ accountAuthTypes[i].SupportedAuthType = supportedAuthType
- if accountAuthType.Credential != nil {
- //populate credentials in accountAuthType
- credential, err := a.storage.FindCredential(nil, accountAuthType.Credential.ID)
- if err != nil || credential == nil {
- return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeCredential, nil, err)
+ if aat.Credential != nil {
+ //populate credentials in accountAuthType
+ credential, err := a.storage.FindCredential(nil, aat.Credential.ID)
+ if err != nil || credential == nil {
+ return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeCredential, nil, err)
+ }
+ credential.AuthType = supportedAuthType.AuthType
+ accountAuthTypes[i].Credential = credential
}
- credential.AuthType = *authType
- accountAuthType.Credential = credential
}
- return accountAuthType, nil
+ return accountAuthTypes, nil
}
func (a *Auth) findAccountAuthTypeByID(account *model.Account, accountAuthTypeID string) (*model.AccountAuthType, error) {
@@ -1050,12 +1089,13 @@ func (a *Auth) findAccountAuthTypeByID(account *model.Account, accountAuthTypeID
return nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccountAuthType, nil)
}
- authType, err := a.storage.FindAuthType(accountAuthType.AuthType.ID)
+ authType, err := a.storage.FindAuthType(accountAuthType.SupportedAuthType.AuthType.ID)
if err != nil || authType == nil {
- return nil, errors.WrapErrorAction(logutils.ActionLoadCache, model.TypeAuthType, logutils.StringArgs(accountAuthType.AuthType.ID), err)
+ return nil, errors.WrapErrorAction(logutils.ActionLoadCache, model.TypeAuthType, logutils.StringArgs(accountAuthType.SupportedAuthType.AuthType.ID), err)
}
- accountAuthType.AuthType = *authType
+ //TODO: Handle retrieving supported auth type/params?
+ accountAuthType.SupportedAuthType = model.SupportedAuthType{AuthType: *authType}
if accountAuthType.Credential != nil {
//populate credentials in accountAuthType
@@ -1069,6 +1109,33 @@ func (a *Auth) findAccountAuthTypeByID(account *model.Account, accountAuthTypeID
return accountAuthType, nil
}
+func (a *Auth) updateAccountIdentifier(context storage.TransactionContext, account *model.Account, accountIdentifier *model.AccountIdentifier) error {
+ if account == nil {
+ return errors.ErrorData(logutils.StatusMissing, model.TypeAccount, nil)
+ }
+ if accountIdentifier == nil {
+ return errors.ErrorData(logutils.StatusMissing, model.TypeAccountIdentifier, nil)
+ }
+
+ newAccountIdentifiers := make([]model.AccountIdentifier, len(account.Identifiers))
+ for j, aIdentifier := range account.Identifiers {
+ if aIdentifier.ID == accountIdentifier.ID {
+ newAccountIdentifiers[j] = *accountIdentifier
+ } else {
+ newAccountIdentifiers[j] = aIdentifier
+ }
+ }
+ account.Identifiers = newAccountIdentifiers
+
+ // update account
+ err := a.storage.SaveAccount(context, account)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionSave, model.TypeAccount, nil, err)
+ }
+
+ return nil
+}
+
func (a *Auth) clearExpiredSessions(identifier string, l *logs.Log) error {
l.Info("clearExpiredSessions")
@@ -1105,9 +1172,9 @@ func (a *Auth) clearExpiredSessions(identifier string, l *logs.Log) error {
return nil
}
-func (a *Auth) applyLogin(anonymous bool, sub string, authType model.AuthType, appOrg model.ApplicationOrganization,
- accountAuthType *model.AccountAuthType, appType model.ApplicationType, externalIDs map[string]string, ipAddress string, deviceType string,
- deviceOS *string, deviceID *string, clientVersion *string, params map[string]interface{}, state string, l *logs.Log) (*model.LoginSession, error) {
+func (a *Auth) applyLogin(anonymous bool, sub string, authType model.AuthType, appOrg model.ApplicationOrganization, account *model.Account,
+ appType model.ApplicationType, ipAddress string, deviceType string, deviceOS *string, deviceID *string, clientVersion *string, params map[string]interface{},
+ state string, l *logs.Log) (*model.LoginSession, error) {
var err error
var loginSession *model.LoginSession
@@ -1138,7 +1205,7 @@ func (a *Auth) applyLogin(anonymous bool, sub string, authType model.AuthType, a
///
///create login session entity
- loginSession, err = a.createLoginSession(anonymous, sub, authType, appOrg, accountAuthType, appType, externalIDs, ipAddress, params, state, device, l)
+ loginSession, err = a.createLoginSession(anonymous, sub, authType, appOrg, account, appType, ipAddress, params, state, device, l)
if err != nil {
return errors.WrapErrorAction(logutils.ActionCreate, model.TypeLoginSession, nil, err)
}
@@ -1199,48 +1266,50 @@ func (a *Auth) createDevice(accountID string, deviceType string, deviceOS *strin
Type: deviceType, OS: *deviceOS, DateCreated: time.Now()}, nil
}
-func (a *Auth) createLoginSession(anonymous bool, sub string, authType model.AuthType,
- appOrg model.ApplicationOrganization, accountAuthType *model.AccountAuthType, appType model.ApplicationType,
- externalIDs map[string]string, ipAddress string, params map[string]interface{}, state string, device *model.Device, l *logs.Log) (*model.LoginSession, error) {
+func (a *Auth) createLoginSession(anonymous bool, sub string, authType model.AuthType, appOrg model.ApplicationOrganization, account *model.Account,
+ appType model.ApplicationType, ipAddress string, params map[string]interface{}, state string, device *model.Device, l *logs.Log) (*model.LoginSession, error) {
//id
- idUUID, _ := uuid.NewUUID()
- id := idUUID.String()
-
- //account auth type
- if !anonymous {
- //sort account auth types by the one used for login
- accountAuthType.Account.SortAccountAuthTypes(accountAuthType.Identifier)
- }
+ id := uuid.NewString()
//access token
orgID := appOrg.Organization.ID
appID := appOrg.Application.ID
- uid := ""
name := ""
email := ""
phone := ""
+ username := ""
permissions := []string{}
scopes := []string{authorization.ScopeGlobal}
+ externalIDs := make(map[string]string)
if !anonymous {
- uid = accountAuthType.Identifier
- name = accountAuthType.Account.Profile.GetFullName()
- email = accountAuthType.Account.Profile.Email
- phone = accountAuthType.Account.Profile.Phone
- permissions = accountAuthType.Account.GetPermissionNames()
- scopes = append(scopes, accountAuthType.Account.GetScopes()...)
- }
- claims := a.getStandardClaims(sub, uid, name, email, phone, rokwireTokenAud, orgID, appID, authType.Code, externalIDs, nil, anonymous, true, appOrg.Application.Admin, appOrg.Organization.System, false, true, idUUID.String())
+ if account == nil {
+ return nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccount, nil)
+ }
+ if emailIdentifier := account.GetAccountIdentifier(IdentifierTypeEmail, ""); emailIdentifier != nil {
+ email = emailIdentifier.Identifier
+ }
+ if phoneIdentifier := account.GetAccountIdentifier(IdentifierTypePhone, ""); phoneIdentifier != nil {
+ phone = phoneIdentifier.Identifier
+ }
+ if usernameIdentifier := account.GetAccountIdentifier(IdentifierTypeUsername, ""); usernameIdentifier != nil {
+ username = usernameIdentifier.Identifier
+ }
+ name = account.Profile.GetFullName()
+ permissions = account.GetPermissionNames()
+ scopes = append(scopes, account.GetScopes()...)
+ for _, external := range account.GetExternalAccountIdentifiers() {
+ externalIDs[external.Code] = external.Identifier
+ }
+ }
+ claims := a.getStandardClaims(sub, name, email, phone, username, rokwireTokenAud, orgID, appID, authType.Code, externalIDs, nil, anonymous, true, appOrg.Application.Admin, appOrg.Organization.System, false, true, id)
accessToken, err := a.buildAccessToken(claims, strings.Join(permissions, ","), strings.Join(scopes, " "))
if err != nil {
return nil, errors.WrapErrorAction(logutils.ActionCreate, logutils.TypeToken, nil, err)
}
//refresh token
- refreshToken, err := a.buildRefreshToken()
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionCreate, logutils.TypeToken, nil, err)
- }
+ refreshToken := utils.GenerateRandomString(refreshTokenLength)
now := time.Now().UTC()
var stateExpires *time.Time
@@ -1249,9 +1318,8 @@ func (a *Auth) createLoginSession(anonymous bool, sub string, authType model.Aut
stateExpires = &stateExpireTime
}
- loginSession := model.LoginSession{ID: id, AppOrg: appOrg, AuthType: authType,
- AppType: appType, Anonymous: anonymous, Identifier: sub, ExternalIDs: externalIDs, AccountAuthType: accountAuthType,
- Device: device, IPAddress: ipAddress, AccessToken: accessToken, RefreshTokens: []string{refreshToken}, Params: params,
+ loginSession := model.LoginSession{ID: id, AppOrg: appOrg, AuthType: authType, AppType: appType, Anonymous: anonymous, Identifier: sub,
+ Account: account, Device: device, IPAddress: ipAddress, AccessToken: accessToken, RefreshTokens: []string{refreshToken}, Params: params,
State: state, StateExpires: stateExpires, DateCreated: now}
return &loginSession, nil
@@ -1288,8 +1356,7 @@ func (a *Auth) deleteLoginSessions(context storage.TransactionContext, loginSess
return nil
}
-func (a *Auth) prepareRegistrationData(authType model.AuthType, identifier string,
- profile model.Profile, preferences map[string]interface{}, l *logs.Log) (*model.Profile, map[string]interface{}, error) {
+func (a *Auth) prepareRegistrationData(authType model.AuthType, identifier string, profile model.Profile, preferences map[string]interface{}, l *logs.Log) (*model.Profile, map[string]interface{}, error) {
//no need to merge from profile BB for new apps
///profile and preferences
@@ -1334,15 +1401,13 @@ func (a *Auth) prepareRegistrationData(authType model.AuthType, identifier strin
return &readyProfile, readyPreferences, nil
}
-func (a *Auth) prepareAccountAuthType(authType model.AuthType, identifier string, accountAuthTypeParams map[string]interface{},
- credential *model.Credential, unverified bool, linked bool) (*model.AccountAuthType, *model.Credential, error) {
+func (a *Auth) prepareAccountAuthType(authType model.AuthType, accountAuthTypeActive bool, accountAuthTypeParams map[string]interface{}, credential *model.Credential) (*model.AccountAuthType, error) {
now := time.Now()
//account auth type
- accountAuthTypeID, _ := uuid.NewUUID()
- active := true
- accountAuthType := &model.AccountAuthType{ID: accountAuthTypeID.String(), AuthType: authType,
- Identifier: identifier, Params: accountAuthTypeParams, Credential: credential, Unverified: unverified, Linked: linked, Active: active, DateCreated: now}
+ accountAuthTypeID := uuid.NewString()
+ accountAuthType := &model.AccountAuthType{ID: accountAuthTypeID, SupportedAuthType: model.SupportedAuthType{AuthType: authType},
+ Params: accountAuthTypeParams, Credential: credential, Active: accountAuthTypeActive, DateCreated: now}
//credential
if credential != nil {
@@ -1350,7 +1415,7 @@ func (a *Auth) prepareAccountAuthType(authType model.AuthType, identifier string
credential.AccountsAuthTypes = append(credential.AccountsAuthTypes, *accountAuthType)
}
- return accountAuthType, credential, nil
+ return accountAuthType, nil
}
func (a *Auth) mergeProfiles(dst model.Profile, src *model.Profile, shared bool) model.Profile {
@@ -1361,8 +1426,6 @@ func (a *Auth) mergeProfiles(dst model.Profile, src *model.Profile, shared bool)
dst.PhotoURL = utils.SetStringIfEmpty(dst.PhotoURL, src.PhotoURL)
dst.FirstName = utils.SetStringIfEmpty(dst.FirstName, src.FirstName)
dst.LastName = utils.SetStringIfEmpty(dst.LastName, src.LastName)
- dst.Email = utils.SetStringIfEmpty(dst.Email, src.Email)
- dst.Phone = utils.SetStringIfEmpty(dst.Phone, src.Phone)
dst.Address = utils.SetStringIfEmpty(dst.Address, src.Address)
dst.ZipCode = utils.SetStringIfEmpty(dst.ZipCode, src.ZipCode)
dst.State = utils.SetStringIfEmpty(dst.State, src.State)
@@ -1428,46 +1491,45 @@ func (a *Auth) getProfileBBData(authType model.AuthType, identifier string, l *l
// creatorPermissions ([]string): an admin user's permissions to validate
// l (*logs.Log): Log object pointer for request
// Returns:
-// Registered account (AccountAuthType): Registered Account object
-func (a *Auth) registerUser(context storage.TransactionContext, authType model.AuthType, userIdentifier string, accountAuthTypeParams map[string]interface{},
- appOrg model.ApplicationOrganization, credential *model.Credential, useSharedProfile bool, externalIDs map[string]string, profile model.Profile, privacy model.Privacy, preferences map[string]interface{},
- username string, permissionNames []string, roleIDs []string, groupIDs []string, scopes []string, creatorPermissions []string, clientVersion *string, l *logs.Log) (*model.AccountAuthType, error) {
-
- //External and anonymous auth is automatically verified, otherwise verified if credential has been verified previously
- unverified := true
- if creatorPermissions == nil {
- if authType.IsExternal || authType.IsAnonymous {
- unverified = false
- } else if credential != nil {
- unverified = !credential.Verified
- }
+// Registered account (Account): Registered Account object
+func (a *Auth) registerUser(context storage.TransactionContext, accountIdentifiers []model.AccountIdentifier, authType model.AuthType, accountAuthTypeActive bool, accountAuthTypeParams map[string]interface{},
+ appOrg model.ApplicationOrganization, credential *model.Credential, externalIDs map[string]string, profile model.Profile, privacy model.Privacy, preferences map[string]interface{},
+ permissionNames []string, roleIDs []string, groupIDs []string, scopes []string, creatorPermissions []string, clientVersion *string, l *logs.Log) (*model.Account, error) {
+ if len(accountIdentifiers) == 0 {
+ return nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccountIdentifier, nil)
}
- accountAuthType, err := a.constructAccount(context, authType, userIdentifier, accountAuthTypeParams, appOrg, credential,
- unverified, externalIDs, profile, privacy, preferences, username, permissionNames, roleIDs, groupIDs, scopes, creatorPermissions, clientVersion, l)
+ account, err := a.constructAccount(context, accountIdentifiers, authType, accountAuthTypeActive, accountAuthTypeParams, appOrg, credential,
+ externalIDs, profile, privacy, preferences, permissionNames, roleIDs, groupIDs, scopes, creatorPermissions, clientVersion, l)
if err != nil {
return nil, errors.WrapErrorAction(logutils.ActionCreate, model.TypeAccount, nil, err)
}
- err = a.storeNewAccountInfo(context, accountAuthType.Account, credential, useSharedProfile, profile)
+ err = a.storeNewAccountInfo(context, *account, credential, profile)
if err != nil {
return nil, errors.WrapErrorAction("storing", "new account information", nil, err)
}
- return accountAuthType, nil
+ return account, nil
}
-func (a *Auth) constructAccount(context storage.TransactionContext, authType model.AuthType, userIdentifier string, accountAuthTypeParams map[string]interface{},
- appOrg model.ApplicationOrganization, credential *model.Credential, unverified bool, externalIDs map[string]string, profile model.Profile, privacy model.Privacy, preferences map[string]interface{},
- username string, permissionNames []string, roleIDs []string, groupIDs []string, scopes []string, assignerPermissions []string, clientVersion *string, l *logs.Log) (*model.AccountAuthType, error) {
+func (a *Auth) constructAccount(context storage.TransactionContext, accountIdentifiers []model.AccountIdentifier, authType model.AuthType, accountAuthTypeActive bool, accountAuthTypeParams map[string]interface{},
+ appOrg model.ApplicationOrganization, credential *model.Credential, externalIDs map[string]string, profile model.Profile, privacy model.Privacy, preferences map[string]interface{},
+ permissionNames []string, roleIDs []string, groupIDs []string, scopes []string, assignerPermissions []string, clientVersion *string, l *logs.Log) (*model.Account, error) {
//create account auth type
- accountAuthType, _, err := a.prepareAccountAuthType(authType, userIdentifier, accountAuthTypeParams, credential, unverified, false)
+ accountAuthType, err := a.prepareAccountAuthType(authType, accountAuthTypeActive, accountAuthTypeParams, credential)
if err != nil {
return nil, errors.WrapErrorAction(logutils.ActionCreate, model.TypeAccountAuthType, nil, err)
}
+ if accountAuthType.Params["user"] != nil {
+ // external auth type, so set account auth type IDs for identifiers
+ for i := range accountIdentifiers {
+ accountIdentifiers[i].AccountAuthTypeID = &accountAuthType.ID
+ }
+ }
//create account object
- accountID, _ := uuid.NewUUID()
+ accountID := accountIdentifiers[0].Account.ID
authTypes := []model.AccountAuthType{*accountAuthType}
//assumes admin creator permissions are always non-nil
@@ -1529,15 +1591,18 @@ func (a *Auth) constructAccount(context storage.TransactionContext, authType mod
scopes = nil
}
- account := model.Account{ID: accountID.String(), AppOrg: appOrg, Permissions: permissions,
- Roles: model.AccountRolesFromAppOrgRoles(roles, true, adminSet), Groups: model.AccountGroupsFromAppOrgGroups(groups, true, adminSet), Scopes: scopes, AuthTypes: authTypes,
- ExternalIDs: externalIDs, Preferences: preferences, Profile: profile, Privacy: privacy, Username: username, DateCreated: time.Now(), MostRecentClientVersion: clientVersion}
+ account := model.Account{ID: accountID, AppOrg: appOrg, AuthTypes: authTypes, Identifiers: accountIdentifiers, Permissions: permissions,
+ Roles: model.AccountRolesFromAppOrgRoles(roles, true, adminSet), Groups: model.AccountGroupsFromAppOrgGroups(groups, true, adminSet), Scopes: scopes,
+ Preferences: preferences, Profile: profile, Privacy: privacy, DateCreated: time.Now(), MostRecentClientVersion: clientVersion}
- accountAuthType.Account = account
- return accountAuthType, nil
+ // account.AuthTypes[0].Account = account
+ // for i := range account.Identifiers {
+ // account.Identifiers[i].Account = account
+ // }
+ return &account, nil
}
-func (a *Auth) storeNewAccountInfo(context storage.TransactionContext, account model.Account, credential *model.Credential, useSharedProfile bool, profile model.Profile) error {
+func (a *Auth) storeNewAccountInfo(context storage.TransactionContext, account model.Account, credential *model.Credential, profile model.Profile) error {
//insert account object - it includes the account auth type
_, err := a.storage.InsertAccount(context, account)
if err != nil {
@@ -1546,221 +1611,266 @@ func (a *Auth) storeNewAccountInfo(context storage.TransactionContext, account m
//insert or update credential
if credential != nil {
- if useSharedProfile {
- //update credential
- err = a.storage.UpdateCredential(context, credential)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeCredential, nil, err)
- }
- } else {
- //create credential
- err = a.storage.InsertCredential(context, credential)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionInsert, model.TypeCredential, nil, err)
- }
+ // if useSharedProfile {
+ // //update credential
+ // err = a.storage.UpdateCredential(context, credential)
+ // if err != nil {
+ // return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeCredential, nil, err)
+ // }
+ // }
+
+ //create credential
+ err = a.storage.InsertCredential(context, credential)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionInsert, model.TypeCredential, nil, err)
}
}
//update profile if shared
- if useSharedProfile {
- err = a.storage.UpdateAccountProfile(context, profile)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeProfile, nil, err)
- }
- }
+ // if useSharedProfile {
+ // err = a.storage.UpdateAccountProfile(context, profile)
+ // if err != nil {
+ // return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeProfile, nil, err)
+ // }
+ // }
return nil
}
-func (a *Auth) checkUsername(context storage.TransactionContext, appOrg *model.ApplicationOrganization, username string) error {
- accounts, err := a.storage.FindAccountsByUsername(context, appOrg, username)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
- }
- if len(accounts) > 0 {
- return errors.ErrorData(logutils.StatusInvalid, model.TypeAccountUsername, logutils.StringArgs(username+" taken")).SetStatus(utils.ErrorStatusUsernameTaken)
- }
+func (a *Auth) linkAccountAuthType(account *model.Account, supportedAuthType model.SupportedAuthType, appOrg model.ApplicationOrganization, appType *model.ApplicationType,
+ creds string, params string) (*string, *model.AccountAuthType, error) {
+ var message *string
+ var aat *model.AccountAuthType
+ var err error
- return nil
-}
+ var accountIdentifier *model.AccountIdentifier
+ tryIdentifierLink := false
+ identifierImpl := a.getIdentifierTypeImpl(creds, nil, nil)
+ if identifierImpl != nil {
+ accountIdentifier = account.GetAccountIdentifier(identifierImpl.getCode(), identifierImpl.getIdentifier())
-func (a *Auth) linkAccountAuthType(account model.Account, authType model.AuthType, appOrg model.ApplicationOrganization,
- creds string, params string, l *logs.Log) (string, *model.AccountAuthType, error) {
- authImpl, err := a.getAuthTypeImpl(authType)
- if err != nil {
- return "", nil, errors.WrapErrorAction(logutils.ActionLoadCache, model.TypeAuthType, nil, err)
+ // only try if an identifier was provided and the account does not already have it (conflicts will be handled if attempted)
+ tryIdentifierLink = (accountIdentifier == nil)
}
- userIdentifier, err := authImpl.getUserIdentifier(creds)
+ authImpl, err := a.getAuthTypeImpl(supportedAuthType)
if err != nil {
- return "", nil, errors.WrapErrorAction(logutils.ActionGet, "user identifier", nil, err)
+ return nil, nil, errors.WrapErrorAction(logutils.ActionLoadCache, model.TypeAuthType, nil, err)
}
- //2. check if the user exists
- newCredsAccount, err := a.storage.FindAccount(nil, appOrg.ID, authType.ID, userIdentifier)
+ aats, err := a.findAccountAuthTypesAndCredentials(account, supportedAuthType)
if err != nil {
- return "", nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
+ return nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccountAuthType, &logutils.FieldArgs{"auth_type_code": supportedAuthType.AuthType.Code}, err)
+ }
+
+ inactiveAats := make([]model.AccountAuthType, 0)
+ for _, aat := range aats {
+ if !aat.Active {
+ inactiveAats = append(inactiveAats, aat)
+ }
}
- if newCredsAccount != nil {
- //if account is current account, attempt sign-in. Otherwise, handle conflict
- if newCredsAccount.ID == account.ID {
- message, aat, err := a.applyLinkVerify(authImpl, authType, &account, userIdentifier, creds, l)
+
+ transaction := func(context storage.TransactionContext) error {
+ if len(inactiveAats) > 0 {
+ // there are inactive account auth types (have not been used to sign in yet), so try to verify one of them using creds
+ var accountAuthType *model.AccountAuthType // do not return this account auth type, so use a new variable
+ message, accountAuthType, err = a.verifyAuthTypeActive(identifierImpl, accountIdentifier, authImpl, aats, account.ID, creds, params, appOrg)
if err != nil {
- return "", nil, err
- }
- if message != "" {
- return "", nil, errors.ErrorData("incomplete", "verification", nil).SetStatus(utils.ErrorStatusUnverified)
+ return err
}
- if aat != nil {
+ if accountAuthType != nil {
for i, accAuthType := range account.AuthTypes {
- if accAuthType.ID == aat.ID {
- account.AuthTypes[i] = *aat
+ if accAuthType.ID == accountAuthType.ID {
+ account.AuthTypes[i] = *accountAuthType
break
}
}
}
- return "", nil, nil
+
+ updateIdentifier := accountIdentifier != nil && !accountIdentifier.Verified
+ if updateIdentifier && message == nil {
+ accountIdentifier.Verified = true
+ err := a.storage.UpdateAccountIdentifier(context, *accountIdentifier)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeAccountIdentifier, nil, err)
+ }
+ }
+
+ return nil
+ }
+
+ if !authImpl.allowMultiple() && len(aats) > 0 {
+ // only one account auth type of this type is allowed, so try linking only the identifier
+ if tryIdentifierLink {
+ message, err = a.linkAccountIdentifier(context, account, identifierImpl)
+ if err != nil {
+ return errors.WrapErrorAction("linking", model.TypeAccountIdentifier, nil, err)
+ }
+
+ return nil
+ }
+
+ return errors.ErrorData(logutils.StatusInvalid, model.TypeAuthType, &logutils.FieldArgs{"allow_multiple": false, "code": supportedAuthType.AuthType.Code})
}
- err = a.handleAccountAuthTypeConflict(*newCredsAccount, authType.ID, userIdentifier, false)
+ //apply sign up
+ signUpMessage, _, credential, err := authImpl.signUp(identifierImpl, &account.ID, appOrg, creds, params)
if err != nil {
- return "", nil, err
+ return errors.WrapErrorAction("signing up", "user", nil, err)
+ }
+ if signUpMessage != "" {
+ message = &signUpMessage
}
- }
- credID := uuid.NewString()
+ if credential != nil {
+ credential.AuthType.ID = supportedAuthType.AuthType.ID
+ }
- //apply sign up
- message, credentialValue, err := authImpl.signUp(authType, appOrg, creds, params, credID, l)
- if err != nil {
- return "", nil, errors.WrapErrorAction("signing up", "user", nil, err)
- }
+ var accountAuthTypeParams map[string]interface{}
+ if supportedAuthType.AuthType.Code == AuthTypeWebAuthn && appType != nil {
+ accountAuthTypeParams = map[string]interface{}{"app_type_identifier": appType.Identifier}
+ }
+ aat, err = a.prepareAccountAuthType(supportedAuthType.AuthType, false, accountAuthTypeParams, credential)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionCreate, model.TypeAccountAuthType, nil, err)
+ }
+ aat.Account = *account
- //credential
- var credential *model.Credential
- if credentialValue != nil {
- now := time.Now()
- credential = &model.Credential{ID: credID, AccountsAuthTypes: nil, Value: credentialValue, Verified: false,
- AuthType: authType, DateCreated: now, DateUpdated: &now}
- }
+ err = a.registerAccountAuthType(context, *aat, credential, nil, false)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionRegister, model.TypeAccountAuthType, nil, err)
+ }
- accountAuthType, credential, err := a.prepareAccountAuthType(authType, userIdentifier, nil, credential, true, true)
- if err != nil {
- return "", nil, errors.WrapErrorAction(logutils.ActionCreate, model.TypeAccountAuthType, nil, err)
+ if tryIdentifierLink {
+ message, err = a.linkAccountIdentifier(context, account, identifierImpl)
+ if err != nil {
+ return errors.WrapErrorAction("linking", model.TypeAccountIdentifier, nil, err)
+ }
+ }
+
+ return nil
}
- accountAuthType.Account = account
- err = a.registerAccountAuthType(*accountAuthType, credential, nil, l)
+ err = a.storage.PerformTransaction(transaction)
if err != nil {
- return "", nil, errors.WrapErrorAction(logutils.ActionRegister, model.TypeAccountAuthType, nil, err)
+ return nil, nil, err
}
- return message, accountAuthType, nil
+ return message, aat, nil
}
-func (a *Auth) applyLinkVerify(authImpl authType, authType model.AuthType, account *model.Account,
- userIdentifier string, creds string, l *logs.Log) (string, *model.AccountAuthType, error) {
- //find account auth type
- accountAuthType, err := a.findAccountAuthType(account, &authType, userIdentifier)
- if accountAuthType == nil {
- return "", nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccountAuthType, nil, err)
+func (a *Auth) verifyAuthTypeActive(identifierImpl identifierType, accountIdentifier *model.AccountIdentifier, authImpl authType, accountAuthTypes []model.AccountAuthType, accountID string,
+ creds string, params string, appOrg model.ApplicationOrganization) (*string, *model.AccountAuthType, error) {
+ if accountIdentifier != nil && identifierImpl.requireVerificationForSignIn() {
+ err := identifierImpl.checkVerified(accountIdentifier, appOrg.Application.Name)
+ if err != nil {
+ return nil, nil, errors.WrapErrorData(logutils.StatusInvalid, model.TypeAccountIdentifier, &logutils.FieldArgs{"verified": false}, err)
+ }
}
- if !accountAuthType.Linked {
- return "", nil, errors.ErrorData(logutils.StatusInvalid, model.TypeAccountAuthType, &logutils.FieldArgs{"linked": false})
+ message, credID, err := a.checkCredentials(identifierImpl, authImpl, &accountID, accountAuthTypes, creds, params, appOrg)
+ if err != nil {
+ return nil, nil, errors.WrapErrorAction(logutils.ActionVerify, model.TypeCredential, nil, err)
}
- if !accountAuthType.Unverified {
- return "", nil, errors.ErrorData(logutils.StatusInvalid, model.TypeAccountAuthType, &logutils.FieldArgs{"verified": true})
- }
+ for _, aat := range accountAuthTypes {
+ if credID == "" || (aat.Credential != nil && aat.Credential.ID == credID) {
+ aat.Active = true
+ err = a.storage.UpdateAccountAuthType(nil, aat)
+ if err != nil {
+ return nil, nil, errors.WrapErrorAction(logutils.ActionUpdate, model.TypeAccountAuthType, nil, err)
+ }
- var message string
- message, err = a.checkCredentials(authImpl, authType, accountAuthType, creds, l)
- if err != nil {
- return "", nil, errors.WrapErrorAction(logutils.ActionVerify, model.TypeCredential, nil, err)
+ return message, &aat, nil
+ }
}
-
- return message, accountAuthType, nil
+ return nil, nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccountAuthType, &logutils.FieldArgs{"credential_id": credID})
}
-func (a *Auth) linkAccountAuthTypeExternal(account model.Account, authType model.AuthType, appType model.ApplicationType, appOrg model.ApplicationOrganization,
+func (a *Auth) linkAccountAuthTypeExternal(account *model.Account, supportedAuthType model.SupportedAuthType, appType model.ApplicationType, appOrg model.ApplicationOrganization,
creds string, params string, l *logs.Log) (*model.AccountAuthType, error) {
- authImpl, err := a.getExternalAuthTypeImpl(authType)
+ authImpl, err := a.getExternalAuthTypeImpl(supportedAuthType.AuthType)
if err != nil {
return nil, errors.WrapErrorAction(logutils.ActionLoadCache, model.TypeAuthType, nil, err)
}
- externalUser, _, _, err := authImpl.externalLogin(authType, appType, appOrg, creds, params, l)
+ externalUser, _, _, err := authImpl.externalLogin(supportedAuthType.AuthType, appType, appOrg, creds, params, l)
if err != nil {
return nil, errors.WrapErrorAction("logging in", "external user", nil, err)
}
- //2. check if the user exists
- newCredsAccount, err := a.storage.FindAccount(nil, appOrg.ID, authType.ID, externalUser.Identifier)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
+ // get the correct code for the external identifier from the external IDs map
+ code := ""
+ for k, v := range externalUser.ExternalIDs {
+ if v == externalUser.Identifier {
+ code = k
+ }
}
- //cannot link creds if an account already exists for new creds
- if newCredsAccount != nil {
- return nil, errors.ErrorData("existing", model.TypeAccount, nil).SetStatus(utils.ErrorStatusAlreadyExists)
+ if code == "" && externalUser.Email == externalUser.Identifier {
+ code = IdentifierTypeEmail
}
- accountAuthTypeParams := map[string]interface{}{}
- accountAuthTypeParams["user"] = externalUser
-
- accountAuthType, credential, err := a.prepareAccountAuthType(authType, externalUser.Identifier, accountAuthTypeParams, nil, false, true)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionCreate, model.TypeAccountAuthType, nil, err)
- }
- accountAuthType.Account = account
+ var accountAuthType *model.AccountAuthType
+ transaction := func(context storage.TransactionContext) error {
+ newCredsAccount, err := a.storage.FindAccount(context, appOrg.ID, code, externalUser.Identifier)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
+ }
+ //cannot link creds if an account already exists for new creds
+ if newCredsAccount != nil {
+ return errors.ErrorData("existing", model.TypeAccount, nil).SetStatus(utils.ErrorStatusAlreadyExists)
+ }
- for k, v := range externalUser.ExternalIDs {
- if account.ExternalIDs == nil {
- account.ExternalIDs = make(map[string]string)
+ accountAuthTypeParams := map[string]interface{}{"user": externalUser}
+ accountAuthType, err = a.prepareAccountAuthType(supportedAuthType.AuthType, true, accountAuthTypeParams, nil)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionCreate, model.TypeAccountAuthType, nil, err)
}
- if account.ExternalIDs[k] == "" {
- account.ExternalIDs[k] = v
+
+ updatedIdentifiers := a.updateExternalIdentifiers(account, accountAuthType.ID, externalUser, true)
+
+ accountAuthType.Account = *account
+ err = a.registerAccountAuthType(context, *accountAuthType, nil, account.Identifiers, updatedIdentifiers)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionRegister, model.TypeAccountAuthType, nil, err)
}
+
+ return nil
}
- err = a.registerAccountAuthType(*accountAuthType, credential, account.ExternalIDs, l)
+ err = a.storage.PerformTransaction(transaction)
if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionRegister, model.TypeAccountAuthType, nil, err)
+ return nil, err
}
return accountAuthType, nil
}
-func (a *Auth) registerAccountAuthType(accountAuthType model.AccountAuthType, credential *model.Credential, externalIDs map[string]string, l *logs.Log) error {
+func (a *Auth) registerAccountAuthType(context storage.TransactionContext, accountAuthType model.AccountAuthType, credential *model.Credential, accountIdentifiers []model.AccountIdentifier, updatedIdentifiers bool) error {
var err error
if credential != nil {
//TODO - in one transaction
- if err = a.storage.InsertCredential(nil, credential); err != nil {
+ if err = a.storage.InsertCredential(context, credential); err != nil {
return errors.WrapErrorAction(logutils.ActionInsert, model.TypeCredential, nil, err)
}
}
- err = a.storage.InsertAccountAuthType(accountAuthType)
+ err = a.storage.InsertAccountAuthType(context, accountAuthType)
if err != nil {
- return errors.WrapErrorAction(logutils.ActionInsert, model.TypeAccount, nil, err)
+ return errors.WrapErrorAction(logutils.ActionInsert, model.TypeAccountAuthType, nil, err)
}
- if externalIDs != nil {
- err = a.storage.UpdateAccountExternalIDs(accountAuthType.Account.ID, externalIDs)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionUpdate, "account external IDs", nil, err)
- }
-
- err = a.storage.UpdateLoginSessionExternalIDs(accountAuthType.Account.ID, externalIDs)
+ if updatedIdentifiers {
+ err = a.storage.UpdateAccountIdentifiers(context, accountAuthType.Account.ID, accountIdentifiers)
if err != nil {
- return errors.WrapErrorAction(logutils.ActionUpdate, "login session external IDs", nil, err)
+ return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeAccountIdentifier, nil, err)
}
}
return nil
}
-func (a *Auth) unlinkAccountAuthType(accountID string, authenticationType string, appTypeIdentifier string, identifier string, l *logs.Log) (*model.Account, error) {
+func (a *Auth) unlinkAccountAuthType(accountID string, accountAuthTypeID *string, authenticationType *string, identifier *string, admin bool) (*model.Account, error) {
account, err := a.storage.FindAccountByID(nil, accountID)
if err != nil {
return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
@@ -1768,14 +1878,35 @@ func (a *Auth) unlinkAccountAuthType(accountID string, authenticationType string
if account == nil {
return nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccount, &logutils.FieldArgs{"id": accountID})
}
+ if len(account.AuthTypes) < 2 {
+ return nil, errors.ErrorData(logutils.StatusInvalid, model.TypeAccount, &logutils.FieldArgs{"auth_types": len(account.AuthTypes)})
+ }
for i, aat := range account.AuthTypes {
// unlink auth type with matching code and identifier
- if aat.AuthType.Code == authenticationType && aat.Identifier == identifier {
- aat.Account = *account
- err := a.removeAccountAuthType(aat)
+ aatIDMatch := accountAuthTypeID != nil && aat.ID == *accountAuthTypeID
+ aatCodeMatch := authenticationType != nil && utils.Contains(aat.SupportedAuthType.AuthType.Aliases, *authenticationType)
+ if aatIDMatch || aatCodeMatch {
+ transaction := func(context storage.TransactionContext) error {
+ if aatCodeMatch && identifier != nil && (!aat.SupportedAuthType.AuthType.IsExternal || admin) {
+ err = a.unlinkAccountIdentifier(context, account, nil, identifier, admin)
+ if err != nil {
+ return errors.WrapErrorAction("unlinking", model.TypeAccountIdentifier, &logutils.FieldArgs{"account_id": account.ID, "identifier": *identifier}, err)
+ }
+ }
+
+ aat.Account = *account
+ err = a.removeAccountAuthType(context, aat)
+ if err != nil {
+ return errors.WrapErrorAction("unlinking", model.TypeAccountAuthType, nil, err)
+ }
+
+ return nil
+ }
+
+ err = a.storage.PerformTransaction(transaction)
if err != nil {
- return nil, errors.WrapErrorAction("unlinking", model.TypeAccountAuthType, nil, err)
+ return nil, err
}
account.AuthTypes = append(account.AuthTypes[:i], account.AuthTypes[i+1:]...)
@@ -1786,15 +1917,77 @@ func (a *Auth) unlinkAccountAuthType(accountID string, authenticationType string
return account, nil
}
-func (a *Auth) handleAccountAuthTypeConflict(account model.Account, authTypeID string, userIdentifier string, newAccount bool) error {
- aat := account.GetAccountAuthType(authTypeID, userIdentifier)
- if aat == nil || !aat.Unverified {
+func (a *Auth) linkAccountIdentifier(context storage.TransactionContext, account *model.Account, identifierImpl identifierType) (*string, error) {
+ identifier := identifierImpl.getIdentifier()
+
+ existingIdentifierAccount, err := a.storage.FindAccount(context, account.AppOrg.ID, identifierImpl.getCode(), identifier)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
+ }
+ if existingIdentifierAccount != nil {
+ err = a.handleAccountIdentifierConflict(*existingIdentifierAccount, identifierImpl, false)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ message, accountIdentifier, err := identifierImpl.buildIdentifier(&account.ID, account.AppOrg.Application.Name)
+ if err != nil {
+ return nil, errors.WrapErrorAction("building", model.TypeAccountIdentifier, &logutils.FieldArgs{"account_id": account.ID, "identifier": identifier}, err)
+ }
+ accountIdentifier.Linked = true
+
+ account.Identifiers = append(account.Identifiers, *accountIdentifier)
+ err = a.storage.InsertAccountIdentifier(context, *accountIdentifier)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionCreate, model.TypeAccountIdentifier, &logutils.FieldArgs{"account_id": account.ID, "identifier": identifier}, err)
+ }
+
+ return &message, nil
+}
+
+func (a *Auth) unlinkAccountIdentifier(context storage.TransactionContext, account *model.Account, accountIdentifierID *string, identifier *string, admin bool) error {
+ if len(account.Identifiers) < 2 {
+ return errors.ErrorData(logutils.StatusInvalid, model.TypeAccount, &logutils.FieldArgs{"identifiers": len(account.Identifiers)})
+ }
+
+ verifiedIdentifiers := account.GetVerifiedAccountIdentifiers()
+ if len(verifiedIdentifiers) == 1 {
+ idMatch := accountIdentifierID != nil && verifiedIdentifiers[0].ID == *accountIdentifierID
+ identifierMatch := identifier != nil && verifiedIdentifiers[0].Identifier == *identifier
+ if idMatch || identifierMatch {
+ return errors.ErrorData(logutils.StatusInvalid, model.TypeAccount, &logutils.FieldArgs{"verified_identifiers": 1})
+ }
+ }
+
+ for i, id := range account.Identifiers {
+ // unlink identifier with matching identifier value (do not directly unlink identifiers with associated auth type unless admin)
+ if id.AccountAuthTypeID == nil || admin {
+ if (identifier != nil && *identifier == id.Identifier) || (accountIdentifierID != nil && *accountIdentifierID == id.ID) {
+ id.Account = *account
+ err := a.storage.DeleteAccountIdentifier(context, id)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionDelete, model.TypeAccountIdentifier, nil, err)
+ }
+
+ account.Identifiers = append(account.Identifiers[:i], account.Identifiers[i+1:]...)
+ break
+ }
+ }
+ }
+
+ return nil
+}
+
+func (a *Auth) handleAccountIdentifierConflict(account model.Account, identifierImpl identifierType, newAccount bool) error {
+ accountIdentifier := account.GetAccountIdentifier(identifierImpl.getCode(), identifierImpl.getIdentifier())
+ if accountIdentifier == nil || accountIdentifier.Verified {
//cannot link creds if a verified account already exists for new creds
return errors.ErrorData("existing", model.TypeAccount, nil).SetStatus(utils.ErrorStatusAlreadyExists)
}
//if this is the only auth type (this will only be possible for accounts created through sign up that were never verified/used)
- if len(account.AuthTypes) == 1 {
+ if len(account.Identifiers) == 1 {
//if signing up, do not replace previous unverified account created through sign up
if newAccount {
return errors.ErrorData("existing", model.TypeAccount, nil).SetStatus(utils.ErrorStatusAlreadyExists)
@@ -1806,7 +1999,7 @@ func (a *Auth) handleAccountAuthTypeConflict(account model.Account, authTypeID s
}
} else {
//Otherwise unlink auth type from account
- err := a.removeAccountAuthType(*aat)
+ err := a.storage.DeleteAccountIdentifier(nil, *accountIdentifier)
if err != nil {
return errors.WrapErrorAction(logutils.ActionDelete, model.TypeAccountAuthType, nil, err)
}
@@ -1815,34 +2008,30 @@ func (a *Auth) handleAccountAuthTypeConflict(account model.Account, authTypeID s
return nil
}
-func (a *Auth) removeAccountAuthType(aat model.AccountAuthType) error {
- transaction := func(context storage.TransactionContext) error {
- //1. delete account auth type in account
- err := a.storage.DeleteAccountAuthType(context, aat)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionDelete, model.TypeAccountAuthType, nil, err)
- }
+func (a *Auth) removeAccountAuthType(context storage.TransactionContext, aat model.AccountAuthType) error {
+ //1. delete account auth type in account
+ err := a.storage.DeleteAccountAuthType(context, aat)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionDelete, model.TypeAccountAuthType, nil, err)
+ }
- //2. delete credential if it exists
- if aat.Credential != nil {
- err = a.removeAccountAuthTypeCredential(context, aat)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionDelete, model.TypeCredential, nil, err)
- }
+ //2. delete credential if it exists
+ if aat.Credential != nil {
+ err = a.removeAccountAuthTypeCredential(context, aat)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionDelete, model.TypeCredential, nil, err)
}
+ }
- //3. delete login sessions using unlinked account auth type (if unverified no sessions should exist)
- if !aat.Unverified {
- err = a.storage.DeleteLoginSessionsByAccountAuthTypeID(context, aat.ID)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionDelete, model.TypeLoginSession, nil, err)
- }
+ //3. delete identifiers with matching account auth type ID
+ if aat.Params["user"] != nil {
+ err = a.storage.DeleteExternalAccountIdentifiers(context, aat)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionDelete, model.TypeAccountIdentifier, &logutils.FieldArgs{"external": true, "account_auth_type_id": aat.ID}, err)
}
-
- return nil
}
- return a.storage.PerformTransaction(transaction)
+ return nil
}
func (a *Auth) removeAccountAuthTypeCredential(context storage.TransactionContext, aat model.AccountAuthType) error {
@@ -1992,7 +2181,7 @@ func (a *Auth) buildAccessTokenForServiceAccount(account model.ServiceAccount, a
aud = strings.Join(services, ",")
}
- claims := a.getStandardClaims(account.AccountID, "", account.Name, "", "", aud, orgID, appID, authType, nil, nil, false, true, false, false, true, account.FirstParty, "")
+ claims := a.getStandardClaims(account.AccountID, account.Name, "", "", "", aud, orgID, appID, authType, nil, nil, false, true, false, false, true, account.FirstParty, "")
accessToken, err := a.buildAccessToken(claims, strings.Join(permissions, ","), scope)
if err != nil {
return "", nil, errors.WrapErrorAction(logutils.ActionCreate, logutils.TypeToken, nil, err)
@@ -2000,6 +2189,16 @@ func (a *Auth) buildAccessTokenForServiceAccount(account model.ServiceAccount, a
return accessToken, &model.AppOrgPair{AppID: appID, OrgID: orgID}, nil
}
+func (a *Auth) registerIdentifierType(name string, identifier identifierType) error {
+ if _, ok := a.identifierTypes[name]; ok {
+ return errors.ErrorData(logutils.StatusFound, typeIdentifierType, &logutils.FieldArgs{"name": name})
+ }
+
+ a.identifierTypes[name] = identifier
+
+ return nil
+}
+
func (a *Auth) registerAuthType(name string, auth authType) error {
if _, ok := a.authTypes[name]; ok {
return errors.ErrorData(logutils.StatusFound, model.TypeAuthType, &logutils.FieldArgs{"name": name})
@@ -2050,77 +2249,124 @@ func (a *Auth) registerMfaType(name string, mfa mfaType) error {
return nil
}
-func (a *Auth) validateAuthType(authenticationType string, appTypeIdentifier string, orgID string) (*model.AuthType, *model.ApplicationType, *model.ApplicationOrganization, error) {
+func (a *Auth) validateAuthType(authenticationType string, appTypeIdentifier *string, appID *string, orgID string) (*model.SupportedAuthType, *model.ApplicationType, *model.ApplicationOrganization, error) {
//get the auth type
authType, err := a.storage.FindAuthType(authenticationType)
if err != nil || authType == nil {
return nil, nil, nil, errors.WrapErrorAction(logutils.ActionValidate, model.TypeAuthType, logutils.StringArgs(authenticationType), err)
}
- //get the app type
- applicationType, err := a.storage.FindApplicationType(appTypeIdentifier)
+ //get the app type and app org
+ applicationType, appOrg, err := a.validateAppOrg(appTypeIdentifier, appID, orgID)
if err != nil {
- return nil, nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeApplicationType, logutils.StringArgs(appTypeIdentifier), err)
+ return nil, nil, nil, errors.WrapErrorAction(logutils.ActionValidate, model.TypeApplicationOrganization, nil, err)
+ }
+
+ //check if the auth type is supported for this application and organization
+ if applicationType != nil {
+ supportedAuthType := appOrg.FindSupportedAuthType(*applicationType, *authType)
+ if supportedAuthType == nil {
+ return nil, nil, nil, errors.ErrorAction(logutils.ActionValidate, "not supported auth type for application and organization", &logutils.FieldArgs{"app_type_id": applicationType.ID, "auth_type_id": authType.ID})
+ }
+ return supportedAuthType, applicationType, appOrg, nil
}
- if applicationType == nil {
- return nil, nil, nil, errors.ErrorData(logutils.StatusMissing, model.TypeApplicationType, logutils.StringArgs(appTypeIdentifier))
+
+ for _, appType := range appOrg.Application.Types {
+ supportedAuthType := appOrg.FindSupportedAuthType(appType, *authType)
+ if supportedAuthType != nil {
+ appTypeValue := appType
+ return supportedAuthType, &appTypeValue, appOrg, nil
+ }
+ }
+ return nil, nil, nil, errors.ErrorData(logutils.StatusInvalid, model.TypeAuthType, &logutils.FieldArgs{"app_org_id": appOrg.ID, "auth_type": authenticationType})
+}
+
+func (a *Auth) validateAppOrg(appTypeIdentifier *string, appID *string, orgID string) (*model.ApplicationType, *model.ApplicationOrganization, error) {
+ var applicationID string
+ var applicationType *model.ApplicationType
+ var err error
+ if appID != nil {
+ applicationID = *appID
+ } else if appTypeIdentifier != nil {
+ applicationType, err = a.storage.FindApplicationType(*appTypeIdentifier)
+ if err != nil {
+ return nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeApplicationType, logutils.StringArgs(*appTypeIdentifier), err)
+
+ }
+ if applicationType == nil {
+ return nil, nil, errors.ErrorData(logutils.StatusMissing, model.TypeApplicationType, logutils.StringArgs(*appTypeIdentifier))
+ }
+ applicationID = applicationType.Application.ID
}
//get the app org
- applicationID := applicationType.Application.ID
appOrg, err := a.storage.FindApplicationOrganization(applicationID, orgID)
if err != nil {
- return nil, nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeApplicationOrganization, &logutils.FieldArgs{"app_id": applicationID, "org_id": orgID}, err)
+ return nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeApplicationOrganization, &logutils.FieldArgs{"app_id": applicationID, "org_id": orgID}, err)
}
if appOrg == nil {
- return nil, nil, nil, errors.ErrorData(logutils.StatusMissing, model.TypeApplicationOrganization, &logutils.FieldArgs{"app_id": applicationID, "org_id": orgID})
- }
-
- //check if the auth type is supported for this application and organization
- if !appOrg.IsAuthTypeSupported(*applicationType, *authType) {
- return nil, nil, nil, errors.ErrorAction(logutils.ActionValidate, "not supported auth type for application and organization", &logutils.FieldArgs{"app_type_id": applicationType.ID, "auth_type_id": authType.ID})
+ return nil, nil, errors.ErrorData(logutils.StatusMissing, model.TypeApplicationOrganization, &logutils.FieldArgs{"app_id": applicationID, "org_id": orgID})
}
- return authType, applicationType, appOrg, nil
+ return applicationType, appOrg, nil
}
-func (a *Auth) validateAuthTypeForAppOrg(authenticationType string, appID string, orgID string) (*model.AuthType, *model.ApplicationOrganization, error) {
- authType, err := a.storage.FindAuthType(authenticationType)
- if err != nil || authType == nil {
- return nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAuthType, logutils.StringArgs(authenticationType), err)
- }
+func (a *Auth) getIdentifierTypeImpl(identifierJSON string, identifierCode *string, userIdentifier *string) identifierType {
+ if identifierCode != nil && userIdentifier != nil {
+ code := *identifierCode
+ if code == illinoisOIDCCode {
+ // backwards compatibility: if an OIDC auth type is used, illinois_oidc was provided, so use uin as the identifier code
+ code = defaultIllinoisOIDCIdentifier
+ } else if code == string(phoneverifier.TypeTwilio) {
+ code = IdentifierTypePhone
+ }
- appOrg, err := a.storage.FindApplicationOrganization(appID, orgID)
- if err != nil {
- return nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeApplicationOrganization, &logutils.FieldArgs{"app_id": appID, "org_id": orgID}, err)
- }
- if appOrg == nil {
- return nil, nil, errors.ErrorData(logutils.StatusMissing, model.TypeApplicationOrganization, &logutils.FieldArgs{"app_id": appID, "org_id": orgID})
+ identifierMap := map[string]string{code: *userIdentifier}
+ identifierBytes, err := json.Marshal(identifierMap)
+ if err != nil {
+ a.logger.Errorf("error marshalling json for identifierType: %v", err)
+ return nil
+ }
+ identifierJSON = string(identifierBytes)
}
- for _, appType := range appOrg.Application.Types {
- if appOrg.IsAuthTypeSupported(appType, *authType) {
- return authType, appOrg, nil
+ if identifierJSON != "" {
+ for code, identifierImpl := range a.identifierTypes {
+ if code == IdentifierTypeExternal {
+ continue
+ }
+ if strings.Contains(identifierJSON, code) {
+ specificIdentifierImpl, err := identifierImpl.withIdentifier(identifierJSON)
+ if err == nil && specificIdentifierImpl.getIdentifier() != "" {
+ return specificIdentifierImpl
+ }
+ }
+ }
+
+ // default to the external identifier type
+ specificExternalImpl, err := a.identifierTypes[IdentifierTypeExternal].withIdentifier(identifierJSON)
+ if err == nil && specificExternalImpl.getIdentifier() != "" {
+ return specificExternalImpl
}
}
- return nil, nil, errors.ErrorData(logutils.StatusInvalid, model.TypeAuthType, &logutils.FieldArgs{"app_org_id": appOrg.ID, "auth_type": authenticationType})
+ return nil
}
-func (a *Auth) getAuthTypeImpl(authType model.AuthType) (authType, error) {
- if auth, ok := a.authTypes[authType.Code]; ok {
- return auth, nil
+func (a *Auth) getAuthTypeImpl(supportedAuthType model.SupportedAuthType) (authType, error) {
+ if auth, ok := a.authTypes[supportedAuthType.AuthType.Code]; ok {
+ return auth.withParams(supportedAuthType.Params)
}
- return nil, errors.ErrorData(logutils.StatusInvalid, model.TypeAuthType, logutils.StringArgs(authType.Code))
+ return nil, errors.ErrorData(logutils.StatusInvalid, model.TypeAuthType, logutils.StringArgs(supportedAuthType.AuthType.Code))
}
func (a *Auth) getExternalAuthTypeImpl(authType model.AuthType) (externalAuthType, error) {
key := authType.Code
//illinois_oidc, other_oidc
- if strings.HasSuffix(authType.Code, "_oidc") {
- key = "oidc"
+ if strings.HasSuffix(authType.Code, "_"+AuthTypeOidc) {
+ key = AuthTypeOidc
}
if auth, ok := a.externalAuthTypes[key]; ok {
@@ -2168,15 +2414,6 @@ func (a *Auth) buildCsrfToken(claims tokenauth.Claims) (string, error) {
return tokenauth.GenerateSignedToken(&claims, a.authPrivKey)
}
-func (a *Auth) buildRefreshToken() (string, error) {
- newToken, err := utils.GenerateRandomString(refreshTokenLength)
- if err != nil {
- return "", errors.WrapErrorAction(logutils.ActionCompute, logutils.TypeToken, nil, err)
- }
-
- return newToken, nil
-}
-
// getScopedAccessToken returns a scoped access token with the requested scopes
func (a *Auth) getScopedAccessToken(claims tokenauth.Claims, serviceID string, scopes []authorization.Scope) (string, error) {
aud, scope := a.tokenDataForScopes(scopes)
@@ -2201,8 +2438,8 @@ func (a *Auth) tokenDataForScopes(scopes []authorization.Scope) ([]string, strin
return services, strings.Join(scopeStrings, " ")
}
-func (a *Auth) getStandardClaims(sub string, uid string, name string, email string, phone string, aud string, orgID string, appID string,
- authType string, externalIDs map[string]string, exp *int64, anonymous bool, authenticated bool, admin bool, system bool, service bool, firstParty bool, sessionID string) tokenauth.Claims {
+func (a *Auth) getStandardClaims(sub string, name string, email string, phone string, username string, aud string, orgID string, appID string, authType string, externalIDs map[string]string,
+ exp *int64, anonymous bool, authenticated bool, admin bool, system bool, service bool, firstParty bool, sessionID string) tokenauth.Claims {
return tokenauth.Claims{
StandardClaims: jwt.StandardClaims{
Audience: aud,
@@ -2210,7 +2447,7 @@ func (a *Auth) getStandardClaims(sub string, uid string, name string, email stri
ExpiresAt: a.getExp(exp),
IssuedAt: time.Now().Unix(),
Issuer: a.host,
- }, OrgID: orgID, AppID: appID, AuthType: authType, UID: uid, Name: name, Email: email, Phone: phone,
+ }, OrgID: orgID, AppID: appID, AuthType: authType, Name: name, Email: email, Phone: phone, Username: username,
ExternalIDs: externalIDs, Anonymous: anonymous, Authenticated: authenticated, Admin: admin, System: system,
Service: service, FirstParty: firstParty, SessionID: sessionID,
}
@@ -2329,6 +2566,58 @@ func (a *Auth) updateExternalAccountGroups(account *model.Account, newExternalGr
return updated, nil
}
+func (a *Auth) updateExternalIdentifiers(account *model.Account, accountAuthTypeID string, externalUser *model.ExternalSystemUser, linked bool) bool {
+ updated := false
+ now := time.Now().UTC()
+ for k, v := range externalUser.ExternalIDs {
+ accountIdentifier := account.GetAccountIdentifier(k, "")
+ if accountIdentifier == nil {
+ primary := (v == externalUser.Identifier)
+ newIdentifier := model.AccountIdentifier{ID: uuid.NewString(), Code: k, Identifier: v, Verified: true, Linked: linked, AccountAuthTypeID: &accountAuthTypeID,
+ Sensitive: utils.Contains(externalUser.SensitiveExternalIDs, k), Primary: &primary, Account: model.Account{ID: account.ID}, DateCreated: now}
+ account.Identifiers = append(account.Identifiers, newIdentifier)
+ updated = true
+ } else if accountIdentifier.Identifier != v {
+ now := time.Now().UTC()
+ primary := (v == externalUser.Identifier)
+ accountIdentifier.Identifier = v
+ accountIdentifier.Primary = &primary
+ accountIdentifier.DateUpdated = &now
+ updated = true
+ }
+ }
+
+ if externalUser.Email != "" {
+ hasExternalEmail := false
+ for i, identifier := range account.Identifiers {
+ if identifier.Code == IdentifierTypeEmail {
+ aatMatch := identifier.AccountAuthTypeID != nil && *identifier.AccountAuthTypeID == accountAuthTypeID // have an external email
+ identifierMatch := identifier.AccountAuthTypeID == nil && identifier.Identifier == externalUser.Email // have an internal email matching external email field
+ hasExternalEmail = aatMatch || identifierMatch
+ if (aatMatch && identifier.Identifier != externalUser.Email) || identifierMatch {
+ // update if have mismatching external email or internal email matching external email field
+ primary := (externalUser.Email == externalUser.Identifier)
+ account.Identifiers[i].Identifier = externalUser.Email
+ account.Identifiers[i].Primary = &primary
+ updated = true
+ }
+ if hasExternalEmail {
+ break
+ }
+ }
+ }
+ if !hasExternalEmail {
+ primary := (externalUser.Email == externalUser.Identifier)
+ account.Identifiers = append(account.Identifiers, model.AccountIdentifier{ID: uuid.NewString(), Code: IdentifierTypeEmail, Identifier: externalUser.Email,
+ Verified: externalUser.IsEmailVerified, Linked: linked, Sensitive: true, AccountAuthTypeID: &accountAuthTypeID, Primary: &primary,
+ Account: model.Account{ID: account.ID}, DateCreated: now})
+ updated = true
+ }
+ }
+
+ return updated
+}
+
func (a *Auth) setLogContext(account *model.Account, l *logs.Log) {
accountID := "nil"
if account != nil {
@@ -2365,7 +2654,7 @@ func (a *Auth) storeCoreRegs() error {
// storeCoreServiceAccount stores the service account record for the Core BB
func (a *Auth) storeCoreServiceAccount() {
- coreAccount := model.ServiceAccount{AccountID: a.serviceID, Name: "ROKWIRE Core Building Block", FirstParty: true, DateCreated: time.Now()}
+ coreAccount := model.ServiceAccount{AccountID: a.serviceID, Name: "ROKWIRE Core Building Block", FirstParty: true, DateCreated: time.Now().UTC()}
// Setup core service account if missing
a.storage.InsertServiceAccount(&coreAccount)
}
diff --git a/core/auth/auth_type_code.go b/core/auth/auth_type_code.go
new file mode 100644
index 000000000..0977f8726
--- /dev/null
+++ b/core/auth/auth_type_code.go
@@ -0,0 +1,190 @@
+// Copyright 2023 Board of Trustees of the University of Illinois.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package auth
+
+import (
+ "core-building-block/core/model"
+ "core-building-block/utils"
+ "encoding/json"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/google/uuid"
+ "github.com/rokwire/logging-library-go/v2/errors"
+ "github.com/rokwire/logging-library-go/v2/logutils"
+ "gopkg.in/go-playground/validator.v9"
+)
+
+const (
+ //AuthTypeCode code auth type
+ AuthTypeCode string = "code"
+
+ typeAuthenticationCode string = "authentication code"
+
+ stateKeyCode string = "code"
+
+ typeCodeCreds logutils.MessageDataType = "code creds"
+)
+
+// codeCreds represents the creds struct for code authentication
+type codeCreds struct {
+ Code *string `json:"code,omitempty"`
+}
+
+// Code implementation of authType
+type codeAuthImpl struct {
+ auth *Auth
+ authType string
+}
+
+func (a *codeAuthImpl) signUp(identifierImpl identifierType, accountID *string, appOrg model.ApplicationOrganization, creds string, params string) (string, *model.AccountIdentifier, *model.Credential, error) {
+ identifierChannel, _ := identifierImpl.(authCommunicationChannel)
+ if identifierChannel == nil {
+ return "", nil, nil, errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, logutils.StringArgs(identifierImpl.getCode()))
+ }
+
+ if accountID != nil {
+ return "", nil, nil, nil
+ }
+
+ // we are not linking a code credential, so use the accountID generated for the identifier
+ message, accountIdentifier, err := identifierImpl.buildIdentifier(nil, appOrg.Application.Name)
+ if err != nil {
+ return "", nil, nil, errors.WrapErrorAction("building", "identifier", logutils.StringArgs(identifierImpl.getCode()), err)
+ }
+
+ return message, accountIdentifier, nil, nil
+}
+
+func (a *codeAuthImpl) signUpAdmin(identifierImpl identifierType, appOrg model.ApplicationOrganization, creds string) (map[string]interface{}, *model.AccountIdentifier, *model.Credential, error) {
+ return nil, nil, nil, errors.New(logutils.Unimplemented)
+}
+
+func (a *codeAuthImpl) forgotCredential(identifierImpl identifierType, credential *model.Credential, appOrg model.ApplicationOrganization) (map[string]interface{}, error) {
+ return nil, errors.New(logutils.Unimplemented)
+}
+
+func (a *codeAuthImpl) resetCredential(credential *model.Credential, resetCode *string, params string) (map[string]interface{}, error) {
+ return nil, errors.New(logutils.Unimplemented)
+}
+
+func (a *codeAuthImpl) checkCredentials(identifierImpl identifierType, accountID *string, aats []model.AccountAuthType, creds string, params string, appOrg model.ApplicationOrganization) (string, string, error) {
+ identifierChannel, _ := identifierImpl.(authCommunicationChannel)
+ if identifierChannel == nil {
+ return "", "", errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, logutils.StringArgs(identifierImpl.getCode()))
+ }
+
+ incomingCreds, err := a.parseCreds(creds)
+ if err != nil {
+ return "", "", errors.WrapErrorAction(logutils.ActionParse, typeCodeCreds, nil, err)
+ }
+ incomingCode := ""
+ if incomingCreds.Code != nil {
+ incomingCode = *incomingCreds.Code
+ }
+
+ if identifierChannel.requiresCodeGeneration() {
+ if incomingCode == "" {
+ // generate a new code
+ code := strconv.Itoa(utils.GenerateRandomInt(1000000))
+ padLen := 6 - len(code)
+ if padLen > 0 {
+ code = strings.Repeat("0", padLen) + code
+ }
+
+ // store generated codes in login state collection
+ state := map[string]interface{}{stateKeyCode: code}
+ 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 {
+ return "", "", errors.WrapErrorAction(logutils.ActionCreate, model.TypeLoginState, nil, err)
+ }
+ } else {
+ params := map[string]interface{}{
+ stateKeyCode: *incomingCreds.Code,
+ }
+ loginState, err := a.auth.storage.FindLoginState(appOrg.Application.ID, appOrg.Organization.ID, accountID, params)
+ if err != nil {
+ return "", "", errors.WrapErrorAction(logutils.ActionFind, model.TypeLoginState, nil, err)
+ }
+
+ if loginState == nil {
+ return "", "", errors.ErrorData(logutils.StatusInvalid, "code", logutils.StringArgs(*incomingCreds.Code))
+ }
+
+ return "", "", nil
+ }
+ }
+
+ message, err := identifierChannel.sendCode(appOrg.Application.Name, incomingCode, typeAuthenticationCode, "")
+ if err != nil {
+ return "", "", err
+ }
+
+ return message, "", nil
+}
+
+func (a *codeAuthImpl) withParams(params map[string]interface{}) (authType, error) {
+ return a, nil
+}
+
+func (a *codeAuthImpl) requireIdentifierVerificationForSignIn() bool {
+ return false
+}
+
+func (a *codeAuthImpl) allowMultiple() bool {
+ return false
+}
+
+// Helpers
+
+func (a *codeAuthImpl) parseCreds(creds string) (*codeCreds, error) {
+ var credential codeCreds
+ err := json.Unmarshal([]byte(creds), &credential)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typeCodeCreds, nil, err)
+ }
+ err = validator.New().Struct(credential)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionValidate, typeCodeCreds, nil, err)
+ }
+ return &credential, nil
+}
+
+func (a *codeAuthImpl) mapToCreds(credsMap map[string]interface{}) (*codeCreds, error) {
+ creds, err := utils.JSONConvert[codeCreds, map[string]interface{}](credsMap)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionParse, typeCodeCreds, nil, err)
+ }
+
+ err = validator.New().Struct(creds)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionValidate, typeCodeCreds, nil, err)
+ }
+ return creds, nil
+}
+
+// initCodeAuth initializes and registers a new code auth instance
+func initCodeAuth(auth *Auth) (*codeAuthImpl, error) {
+ code := &codeAuthImpl{auth: auth, authType: AuthTypeCode}
+
+ err := auth.registerAuthType(code.authType, code)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionRegister, model.TypeAuthType, nil, err)
+ }
+
+ return code, nil
+}
diff --git a/core/auth/auth_type_email.go b/core/auth/auth_type_email.go
deleted file mode 100644
index 2f11c18fc..000000000
--- a/core/auth/auth_type_email.go
+++ /dev/null
@@ -1,517 +0,0 @@
-// Copyright 2022 Board of Trustees of the University of Illinois.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package auth
-
-import (
- "core-building-block/core/model"
- "core-building-block/utils"
- "crypto/subtle"
- "encoding/json"
- "fmt"
- "net/url"
- "time"
-
- "github.com/rokwire/logging-library-go/v2/errors"
- "github.com/rokwire/logging-library-go/v2/logs"
- "github.com/rokwire/logging-library-go/v2/logutils"
- "golang.org/x/crypto/bcrypt"
-)
-
-const (
- //AuthTypeEmail email auth type
- AuthTypeEmail string = "email"
-
- typeTime logutils.MessageDataType = "time.Time"
- typeEmailCreds logutils.MessageDataType = "email creds"
- typeEmailParams logutils.MessageDataType = "email params"
-)
-
-// enailCreds represents the creds struct for email auth
-type emailCreds struct {
- Email string `json:"email" bson:"email" validate:"required"`
- Password string `json:"password" bson:"password"`
- VerificationCode string `json:"verification_code" bson:"verification_code"`
- VerificationExpiry time.Time `json:"verification_expiry" bson:"verification_expiry"`
- ResetCode string `json:"reset_code" bson:"reset_code"`
- ResetExpiry time.Time `json:"reset_expiry" bson:"reset_expiry"`
-}
-
-// Email implementation of authType
-type emailAuthImpl struct {
- auth *Auth
- authType string
-}
-
-func (a *emailAuthImpl) signUp(authType model.AuthType, appOrg model.ApplicationOrganization, creds string, params string, newCredentialID string, l *logs.Log) (string, map[string]interface{}, error) {
- type signUpEmailParams struct {
- ConfirmPassword string `json:"confirm_password"`
- }
-
- var sEmailCreds emailCreds
- err := json.Unmarshal([]byte(creds), &sEmailCreds)
- if err != nil {
- return "", nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typeEmailCreds, nil, err)
- }
-
- var sEmailParams signUpEmailParams
- err = json.Unmarshal([]byte(params), &sEmailParams)
- if err != nil {
- return "", nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typeEmailParams, nil, err)
- }
-
- email := sEmailCreds.Email
- password := sEmailCreds.Password
- confirmPassword := sEmailParams.ConfirmPassword
- if len(email) == 0 {
- return "", nil, errors.ErrorData(logutils.StatusMissing, typeEmailCreds, logutils.StringArgs("email"))
- }
- if len(password) == 0 {
- return "", nil, errors.ErrorData(logutils.StatusMissing, typeEmailCreds, logutils.StringArgs("password"))
- }
- if len(confirmPassword) == 0 {
- return "", nil, errors.ErrorData(logutils.StatusMissing, typeEmailParams, logutils.StringArgs("confirm_password"))
- }
- //check if the passwrod matches with the confirm password one
- if password != confirmPassword {
- return "", nil, errors.ErrorData(logutils.StatusInvalid, "mismatching password fields", nil)
- }
-
- emailCreds, err := a.buildCredentials(authType, appOrg.Application.Name, email, password, newCredentialID)
- if err != nil {
- return "", nil, errors.WrapErrorAction("building", "email credentials", nil, err)
- }
-
- return "verification code sent successfully", emailCreds, nil
-}
-
-func (a *emailAuthImpl) signUpAdmin(authType model.AuthType, appOrg model.ApplicationOrganization, identifier string, password string, newCredentialID string) (map[string]interface{}, map[string]interface{}, error) {
- if password == "" {
- password = utils.GenerateRandomPassword(12)
- }
-
- emailCreds, err := a.buildCredentials(authType, appOrg.Application.Name, identifier, password, newCredentialID)
- if err != nil {
- return nil, nil, errors.WrapErrorAction("building", "email credentials", nil, err)
- }
-
- params := map[string]interface{}{"password": password}
- return params, emailCreds, nil
-}
-
-func (a *emailAuthImpl) isCredentialVerified(credential *model.Credential, l *logs.Log) (*bool, *bool, error) {
- if credential.Verified {
- verified := true
- return &verified, nil, nil
- }
-
- //check if email verification is off
- verifyEmail := a.getVerifyEmail(credential.AuthType)
- if !verifyEmail {
- verified := true
- return &verified, nil, nil
- }
-
- //it is unverified
- verified := false
- //check if the verification is expired
- storedCreds, err := mapToEmailCreds(credential.Value)
- if err != nil {
- return nil, nil, errors.WrapErrorAction(logutils.ActionCast, typeEmailCreds, nil, err)
- }
- expired := false
- if storedCreds.VerificationExpiry.Before(time.Now()) {
- expired = true
- }
- return &verified, &expired, nil
-}
-
-func (a *emailAuthImpl) checkCredentials(accountAuthType model.AccountAuthType, creds string, l *logs.Log) (string, error) {
- //get stored credential
- storedCreds, err := mapToEmailCreds(accountAuthType.Credential.Value)
- if err != nil {
- return "", errors.WrapErrorAction(logutils.ActionCast, typeEmailCreds, nil, err)
- }
-
- //get request credential
- type signInPasswordCred struct {
- Password string `json:"password"`
- }
- var sPasswordParams signInPasswordCred
- err = json.Unmarshal([]byte(creds), &sPasswordParams)
- if err != nil {
- return "", errors.WrapErrorAction(logutils.ActionUnmarshal, "sign in password creds", nil, err)
- }
- requestPassword := sPasswordParams.Password
-
- //compare stored and requets ones
- err = bcrypt.CompareHashAndPassword([]byte(storedCreds.Password), []byte(requestPassword))
- if err != nil {
- return "", errors.WrapErrorAction(logutils.ActionValidate, model.TypeCredential, nil, err).SetStatus(utils.ErrorStatusInvalid)
- }
-
- return "", nil
-}
-
-func (a *emailAuthImpl) buildCredentials(authType model.AuthType, appName string, email string, password string, credID string) (map[string]interface{}, error) {
- //password hash
- hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionGenerate, "password hash", nil, err)
- }
-
- //verification code
- code, err := utils.GenerateRandomString(64)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionGenerate, "verification code", nil, err)
- }
-
- verifyEmail := a.getVerifyEmail(authType)
- verifyExpiryTime := a.getVerifyExpiry(authType)
-
- var emailCredValue emailCreds
- if verifyEmail {
- emailCredValue = emailCreds{Email: email, Password: string(hashedPassword), VerificationCode: code, VerificationExpiry: time.Now().Add(time.Hour * time.Duration(verifyExpiryTime))}
- } else {
- emailCredValue = emailCreds{Email: email, Password: string(hashedPassword)}
- }
-
- emailCredValueMap, err := emailCredsToMap(&emailCredValue)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionCast, "map from email creds", nil, err)
- }
-
- if verifyEmail {
- //send verification code
- if err = a.sendVerificationCode(email, appName, code, credID); err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionSend, "verification email", nil, err)
- }
- }
-
- return emailCredValueMap, nil
-}
-
-func (a *emailAuthImpl) getVerifyEmail(authType model.AuthType) bool {
- verifyEmail := true
- verifyEmailParam, ok := authType.Params["verify_email"].(bool)
- if ok {
- verifyEmail = verifyEmailParam
- }
- return verifyEmail
-}
-
-// Time in seconds to wait before sending another verification email
-func (a *emailAuthImpl) getVerifyWaitTime(authType model.AuthType) int {
- //Default is 30 seconds
- verifyWaitTime := 30
- verifyWaitTimeParam, ok := authType.Params["verify_wait_time"].(int)
- if ok {
- verifyWaitTime = verifyWaitTimeParam
- }
- return verifyWaitTime
-}
-
-// Time in hours before verification code expires
-func (a *emailAuthImpl) getVerifyExpiry(authType model.AuthType) int {
- //Default is 24 hours
- verifyExpiry := 24
- verifyExpiryParam, ok := authType.Params["verify_expiry"].(int)
- if ok {
- verifyExpiry = verifyExpiryParam
- }
- return verifyExpiry
-}
-
-func (a *emailAuthImpl) sendVerificationCode(email string, appName string, verificationCode string, credentialID string) error {
- params := url.Values{}
- params.Add("id", credentialID)
- params.Add("code", verificationCode)
- verificationLink := a.auth.host + fmt.Sprintf("/ui/credential/verify?%s", params.Encode())
- subject := "Verify your email address"
- if appName != "" {
- subject += " for " + appName
- }
- body := "Please click the link below to verify your email address: " + verificationLink + "
If you did not request this verification link, please ignore this message."
- return a.auth.emailer.Send(email, subject, body, nil)
-}
-
-func (a *emailAuthImpl) sendPasswordResetEmail(credentialID string, resetCode string, email string, appName string) error {
- params := url.Values{}
- params.Add("id", credentialID)
- params.Add("code", resetCode)
- passwordResetLink := a.auth.host + fmt.Sprintf("/ui/credential/reset?%s", params.Encode())
- subject := "Reset your password"
- if appName != "" {
- subject += " for " + appName
- }
- body := "Please click the link below to reset your password: " + passwordResetLink + "
If you did not request a password reset, please ignore this message."
- return a.auth.emailer.Send(email, subject, body, nil)
-}
-
-func (a *emailAuthImpl) verifyCredential(credential *model.Credential, verification string, l *logs.Log) (map[string]interface{}, error) {
- credBytes, err := json.Marshal(credential.Value)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionMarshal, typeEmailCreds, nil, err)
- }
-
- var creds *emailCreds
- err = json.Unmarshal(credBytes, &creds)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typeEmailCreds, nil, err)
- }
- err = a.compareCode(creds.VerificationCode, verification, creds.VerificationExpiry, l)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionValidate, model.TypeAuthCred, &logutils.FieldArgs{"verification_code": verification}, err)
- }
-
- //Update verification data
- creds.VerificationCode = ""
- creds.VerificationExpiry = time.Time{}
- credsMap, err := emailCredsToMap(creds)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionCast, typeEmailCreds, nil, err)
- }
-
- return credsMap, nil
-}
-
-func (a *emailAuthImpl) sendVerifyCredential(credential *model.Credential, appName string, l *logs.Log) error {
- //Check if verify email is disabled for the given authType
- authType := credential.AuthType
- verifyEmail := a.getVerifyEmail(authType)
- if !verifyEmail {
- return errors.ErrorAction(logutils.ActionSend, logutils.TypeString, logutils.StringArgs("verify email is disabled for authType"))
- }
- verifyWaitTime := a.getVerifyWaitTime(authType)
- verifyExpiryTime := a.getVerifyExpiry(authType)
-
- //Parse credential value to emailCreds
- emailCreds, err := mapToEmailCreds(credential.Value)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionCast, typeEmailCreds, nil, err)
- }
- //Check if previous verification email was sent less than 30 seconds ago
- now := time.Now()
- prevTime := emailCreds.VerificationExpiry.Add(time.Duration(-verifyExpiryTime) * time.Hour)
- if now.Sub(prevTime) < time.Duration(verifyWaitTime)*time.Second {
- return errors.ErrorAction(logutils.ActionSend, "verify code", logutils.StringArgs("resend requested too soon"))
- }
- //verification code
- code, err := utils.GenerateRandomString(64)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionGenerate, "verification code", nil, err)
- }
-
- //send verification email
- if err = a.sendVerificationCode(emailCreds.Email, appName, code, credential.ID); err != nil {
- return errors.WrapErrorAction(logutils.ActionSend, "verification email", nil, err)
- }
-
- //Update verification data in credential value
- emailCreds.VerificationCode = code
- emailCreds.VerificationExpiry = time.Now().Add(time.Hour * time.Duration(verifyExpiryTime))
- credsMap, err := emailCredsToMap(emailCreds)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionCast, "map from email creds", nil, err)
- }
-
- credential.Value = credsMap
- if err = a.auth.storage.UpdateCredential(nil, credential); err != nil {
- return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeCredential, nil, err)
- }
-
- return nil
-}
-
-func (a *emailAuthImpl) restartCredentialVerification(credential *model.Credential, appName string, l *logs.Log) error {
- storedCreds, err := mapToEmailCreds(credential.Value)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionCast, typeEmailCreds, nil, err)
- }
- //Generate new verification code
- newCode, err := utils.GenerateRandomString(64)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionGenerate, "verification code", nil, err)
-
- }
- //send new verification code for future
- if err = a.sendVerificationCode(storedCreds.Email, appName, newCode, credential.ID); err != nil {
- return errors.WrapErrorAction(logutils.ActionSend, "verification email", nil, err)
- }
- //update new verification data in credential value
- storedCreds.VerificationCode = newCode
- storedCreds.VerificationExpiry = time.Now().Add(time.Hour * 24)
- emailCredValueMap, err := emailCredsToMap(storedCreds)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionCast, "map from email creds", nil, err)
- }
-
- err = a.auth.storage.UpdateCredentialValue(credential.ID, emailCredValueMap)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeCredential, nil, err)
- }
- return nil
-}
-
-func (a *emailAuthImpl) compareCode(credCode string, requestCode string, expiryTime time.Time, l *logs.Log) error {
- if expiryTime.Before(time.Now()) {
- return errors.ErrorData("expired", "code", nil)
- }
-
- if subtle.ConstantTimeCompare([]byte(credCode), []byte(requestCode)) == 0 {
- return errors.ErrorData(logutils.StatusInvalid, "code", nil)
- }
- return nil
-}
-
-func (a *emailAuthImpl) resetCredential(credential *model.Credential, resetCode *string, params string, l *logs.Log) (map[string]interface{}, error) {
- //get the data from params
- type Params struct {
- NewPassword string `json:"new_password"`
- ConfirmPassword string `json:"confirm_password"`
- }
-
- var paramsData Params
- err := json.Unmarshal([]byte(params), ¶msData)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typeEmailParams, nil, err)
- }
- newPassword := paramsData.NewPassword
- confirmPassword := paramsData.ConfirmPassword
-
- if len(newPassword) == 0 {
- return nil, errors.ErrorData(logutils.StatusMissing, logutils.TypeString, logutils.StringArgs("new_password"))
- }
- if len(confirmPassword) == 0 {
- return nil, errors.ErrorData(logutils.StatusMissing, logutils.TypeString, logutils.StringArgs("confirm_password"))
- }
- //check if the password matches with the confirm password one
- if newPassword != confirmPassword {
- return nil, errors.ErrorData(logutils.StatusInvalid, "mismatching password fields", nil)
- }
-
- credBytes, err := json.Marshal(credential.Value)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionMarshal, typeEmailCreds, nil, err)
- }
-
- var creds *emailCreds
- err = json.Unmarshal(credBytes, &creds)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typeEmailCreds, nil, err)
- }
- //reset password from link
- if resetCode != nil {
- if creds.ResetExpiry.Before(time.Now()) {
- return nil, errors.ErrorData("expired", "reset expiration time", nil)
- }
- err = bcrypt.CompareHashAndPassword([]byte(creds.ResetCode), []byte(*resetCode))
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionValidate, model.TypeAuthCred, &logutils.FieldArgs{"reset_code": *resetCode}, err)
- }
-
- //Update verification data
- creds.ResetCode = ""
- creds.ResetExpiry = time.Time{}
- }
-
- hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionGenerate, "password hash", nil, err)
- }
-
- //Update verification data
- creds.Password = string(hashedPassword)
- credsMap, err := emailCredsToMap(creds)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionCast, "map from email creds", nil, err)
- }
-
- return credsMap, nil
-}
-
-func (a *emailAuthImpl) forgotCredential(credential *model.Credential, identifier string, appName string, l *logs.Log) (map[string]interface{}, error) {
- emailCreds, err := mapToEmailCreds(credential.Value)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionCast, typeEmailCreds, nil, err)
- }
- resetCode, err := utils.GenerateRandomString(64)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionGenerate, "reset code", nil, err)
-
- }
- hashedResetCode, err := bcrypt.GenerateFromPassword([]byte(resetCode), bcrypt.DefaultCost)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionGenerate, "reset code hash", nil, err)
- }
- emailCreds.ResetCode = string(hashedResetCode)
- emailCreds.ResetExpiry = time.Now().Add(time.Hour * 24)
- err = a.sendPasswordResetEmail(credential.ID, resetCode, identifier, appName)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionSend, "password reset email", nil, err)
- }
- credsMap, err := emailCredsToMap(emailCreds)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionCast, "map from email creds", nil, err)
- }
- return credsMap, nil
-}
-
-func (a *emailAuthImpl) getUserIdentifier(creds string) (string, error) {
- var requestCreds emailCreds
- err := json.Unmarshal([]byte(creds), &requestCreds)
- if err != nil {
- return "", errors.WrapErrorAction(logutils.ActionUnmarshal, typeEmailCreds, nil, err)
- }
-
- return requestCreds.Email, nil
-}
-
-func emailCredsToMap(creds *emailCreds) (map[string]interface{}, error) {
- credBytes, err := json.Marshal(creds)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionMarshal, typeEmailCreds, nil, err)
- }
- var credsMap map[string]interface{}
- err = json.Unmarshal(credBytes, &credsMap)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, "map from email creds", nil, err)
- }
- return credsMap, nil
-}
-
-func mapToEmailCreds(credsMap map[string]interface{}) (*emailCreds, error) {
- credBytes, err := json.Marshal(credsMap)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionMarshal, typeEmailCreds, nil, err)
- }
- var creds emailCreds
- err = json.Unmarshal(credBytes, &creds)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typeEmailCreds, nil, err)
- }
- return &creds, nil
-}
-
-// initEmailAuth initializes and registers a new email auth instance
-func initEmailAuth(auth *Auth) (*emailAuthImpl, error) {
- email := &emailAuthImpl{auth: auth, authType: AuthTypeEmail}
-
- err := auth.registerAuthType(email.authType, email)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionRegister, model.TypeAuthType, nil, err)
- }
-
- return email, nil
-}
diff --git a/core/auth/auth_type_firebase.go b/core/auth/auth_type_firebase.go
index 524df0ab4..1fd6fd9f3 100644
--- a/core/auth/auth_type_firebase.go
+++ b/core/auth/auth_type_firebase.go
@@ -18,7 +18,6 @@ import (
"core-building-block/core/model"
"github.com/rokwire/logging-library-go/v2/errors"
- "github.com/rokwire/logging-library-go/v2/logs"
"github.com/rokwire/logging-library-go/v2/logutils"
)
@@ -33,44 +32,36 @@ type firebaseAuthImpl struct {
authType string
}
-func (a *firebaseAuthImpl) signUp(authType model.AuthType, appOrg model.ApplicationOrganization, creds string, params string, newCredentialID string, l *logs.Log) (string, map[string]interface{}, error) {
- return "", nil, nil
+func (a *firebaseAuthImpl) signUp(identifierImpl identifierType, accountID *string, appOrg model.ApplicationOrganization, creds string, params string) (string, *model.AccountIdentifier, *model.Credential, error) {
+ return "", nil, nil, nil
}
-func (a *firebaseAuthImpl) signUpAdmin(authType model.AuthType, appOrg model.ApplicationOrganization, identifier string, password string, newCredentialID string) (map[string]interface{}, map[string]interface{}, error) {
- return nil, nil, nil
+func (a *firebaseAuthImpl) signUpAdmin(identifierImpl identifierType, appOrg model.ApplicationOrganization, creds string) (map[string]interface{}, *model.AccountIdentifier, *model.Credential, error) {
+ return nil, nil, nil, nil
}
-func (a *firebaseAuthImpl) getUserIdentifier(creds string) (string, error) {
- return "", nil
-}
-
-func (a *firebaseAuthImpl) verifyCredential(credential *model.Credential, verification string, l *logs.Log) (map[string]interface{}, error) {
- return nil, errors.New(logutils.Unimplemented)
-}
-
-func (a *firebaseAuthImpl) sendVerifyCredential(credential *model.Credential, appName string, l *logs.Log) error {
- return nil
+func (a *firebaseAuthImpl) forgotCredential(identifierImpl identifierType, credential *model.Credential, appOrg model.ApplicationOrganization) (map[string]interface{}, error) {
+ return nil, nil
}
-func (a *firebaseAuthImpl) restartCredentialVerification(credential *model.Credential, appName string, l *logs.Log) error {
- return nil
+func (a *firebaseAuthImpl) resetCredential(credential *model.Credential, resetCode *string, params string) (map[string]interface{}, error) {
+ return nil, nil
}
-func (a *firebaseAuthImpl) isCredentialVerified(credential *model.Credential, l *logs.Log) (*bool, *bool, error) {
- return nil, nil, nil
+func (a *firebaseAuthImpl) checkCredentials(identifierImpl identifierType, accountID *string, aats []model.AccountAuthType, creds string, params string, appOrg model.ApplicationOrganization) (string, string, error) {
+ return "", "", nil
}
-func (a *firebaseAuthImpl) checkCredentials(accountAuthType model.AccountAuthType, creds string, l *logs.Log) (string, error) {
- return "", nil
+func (a *firebaseAuthImpl) withParams(params map[string]interface{}) (authType, error) {
+ return a, nil
}
-func (a *firebaseAuthImpl) resetCredential(credential *model.Credential, resetCode *string, params string, l *logs.Log) (map[string]interface{}, error) {
- return nil, nil
+func (a *firebaseAuthImpl) requireIdentifierVerificationForSignIn() bool {
+ return false
}
-func (a *firebaseAuthImpl) forgotCredential(credential *model.Credential, identifier string, appName string, l *logs.Log) (map[string]interface{}, error) {
- return nil, nil
+func (a *firebaseAuthImpl) allowMultiple() bool {
+ return false
}
// initFirebaseAuth initializes and registers a new Firebase auth instance
diff --git a/core/auth/auth_type_oidc.go b/core/auth/auth_type_oidc.go
index d8109b7b1..cd558ec91 100644
--- a/core/auth/auth_type_oidc.go
+++ b/core/auth/auth_type_oidc.go
@@ -21,12 +21,14 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
+ "io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
+ "golang.org/x/oauth2"
"gopkg.in/go-playground/validator.v9"
"github.com/coreos/go-oidc"
@@ -51,6 +53,7 @@ const (
type oidcAuthImpl struct {
auth *Auth
authType string
+ client *http.Client
}
type oidcAuthConfig struct {
@@ -75,10 +78,10 @@ type oidcLoginParams struct {
}
type oidcToken struct {
- IDToken string `json:"id_token" validate:"required"`
+ IDToken string `json:"id_token"`
AccessToken string `json:"access_token" validate:"required"`
RefreshToken string `json:"refresh_token"`
- TokenType string `json:"token_type" validate:"required"`
+ TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
}
@@ -87,6 +90,16 @@ type oidcRefreshParams struct {
RedirectURI string `json:"redirect_uri" bson:"redirect_uri" validate:"required"`
}
+type transport struct {
+ t http.RoundTripper
+ userAgent string
+}
+
+func (adt *transport) RoundTrip(req *http.Request) (*http.Response, error) {
+ req.Header.Add("User-Agent", adt.userAgent)
+ return adt.t.RoundTrip(req)
+}
+
func oidcRefreshParamsFromMap(val map[string]interface{}) (*oidcRefreshParams, error) {
oidcToken, ok := val["oidc_token"].(map[string]interface{})
if !ok {
@@ -117,7 +130,7 @@ func (a *oidcAuthImpl) externalLogin(authType model.AuthType, appType model.Appl
return nil, nil, "", errors.WrapErrorAction(logutils.ActionValidate, typeOidcLoginParams, nil, err)
}
- oidcConfig, err := a.getOidcAuthConfig(authType, appType)
+ oidcConfig, err := a.getOidcAuthConfig(authType, appType.ID)
if err != nil {
return nil, nil, "", errors.WrapErrorAction(logutils.ActionGet, typeOidcAuthConfig, nil, err)
}
@@ -142,7 +155,7 @@ func (a *oidcAuthImpl) refresh(params map[string]interface{}, authType model.Aut
return nil, nil, "", errors.WrapErrorAction(logutils.ActionParse, typeAuthRefreshParams, nil, err)
}
- oidcConfig, err := a.getOidcAuthConfig(authType, appType)
+ oidcConfig, err := a.getOidcAuthConfig(authType, appType.ID)
if err != nil {
return nil, nil, "", errors.WrapErrorAction(logutils.ActionGet, typeOidcAuthConfig, nil, err)
}
@@ -151,7 +164,7 @@ func (a *oidcAuthImpl) refresh(params map[string]interface{}, authType model.Aut
}
func (a *oidcAuthImpl) getLoginURL(authType model.AuthType, appType model.ApplicationType, redirectURI string, l *logs.Log) (string, map[string]interface{}, error) {
- oidcConfig, err := a.getOidcAuthConfig(authType, appType)
+ oidcConfig, err := a.getOidcAuthConfig(authType, appType.ID)
if err != nil {
return "", nil, errors.WrapErrorAction(logutils.ActionGet, typeOidcAuthConfig, nil, err)
}
@@ -202,7 +215,7 @@ func (a *oidcAuthImpl) getLoginURL(authType model.AuthType, appType model.Applic
func (a *oidcAuthImpl) checkToken(idToken string, authType model.AuthType, appType model.ApplicationType, oidcConfig *oidcAuthConfig, l *logs.Log) (string, error) {
var err error
if oidcConfig == nil {
- oidcConfig, err = a.getOidcAuthConfig(authType, appType)
+ oidcConfig, err = a.getOidcAuthConfig(authType, appType.ID)
if err != nil {
return "", errors.WrapErrorAction(logutils.ActionGet, typeOidcAuthConfig, nil, err)
}
@@ -211,8 +224,11 @@ func (a *oidcAuthImpl) checkToken(idToken string, authType model.AuthType, appTy
oidcProvider := oidcConfig.Host
oidcClientID := oidcConfig.ClientID
+ parent := oidc.ClientContext(context.Background(), a.client)
+ ctx := context.WithValue(parent, oauth2.HTTPClient, a.client)
+
// Validate the token
- provider, err := oidc.NewProvider(context.Background(), oidcProvider)
+ provider, err := oidc.NewProvider(ctx, oidcProvider)
if err != nil {
return "", errors.WrapErrorAction(logutils.ActionInitialize, "oidc provider", nil, err)
}
@@ -272,9 +288,13 @@ func (a *oidcAuthImpl) loadOidcTokensAndInfo(bodyData map[string]string, oidcCon
return nil, nil, "", errors.WrapErrorAction(logutils.ActionGet, typeOidcToken, nil, err)
}
- sub, err := a.checkToken(token.IDToken, authType, appType, oidcConfig, l)
- if err != nil {
- return nil, nil, "", errors.WrapErrorAction(logutils.ActionValidate, typeOidcToken, nil, err)
+ sub := ""
+ if token.IDToken != "" {
+ // we should not check the ID token if it is not provided
+ sub, err = a.checkToken(token.IDToken, authType, appType, oidcConfig, l)
+ if err != nil {
+ return nil, nil, "", errors.WrapErrorAction(logutils.ActionValidate, typeOidcToken, nil, err)
+ }
}
userInfoURL := oidcConfig.Host + "/idp/profile/oidc/userinfo"
@@ -292,9 +312,12 @@ func (a *oidcAuthImpl) loadOidcTokensAndInfo(bodyData map[string]string, oidcCon
return nil, nil, "", errors.WrapErrorAction(logutils.ActionUnmarshal, "user info", nil, err)
}
- userClaimsSub, _ := userClaims["sub"].(string)
- if userClaimsSub != sub {
- return nil, nil, "", errors.ErrorData("mismatched", "sub fields", &logutils.FieldArgs{"user info": userClaimsSub, "id token": sub})
+ if sub != "" {
+ // we should only perform this check if we get the ID token
+ userClaimsSub, _ := userClaims["sub"].(string)
+ if userClaimsSub != sub {
+ return nil, nil, "", errors.ErrorData("mismatched", "sub fields", &logutils.FieldArgs{"user info": userClaimsSub, "id token": sub})
+ }
}
identityProviderID, _ := authType.Params["identity_provider"].(string)
@@ -347,8 +370,9 @@ func (a *oidcAuthImpl) loadOidcTokensAndInfo(bodyData map[string]string, oidcCon
externalIDs[k] = externalID
}
- externalUser := model.ExternalSystemUser{Identifier: identifier, ExternalIDs: externalIDs, FirstName: firstName,
- MiddleName: middleName, LastName: lastName, Email: email, Roles: roles, Groups: groups, SystemSpecific: systemSpecific}
+ externalUser := model.ExternalSystemUser{Identifier: identifier, ExternalIDs: externalIDs, SensitiveExternalIDs: identityProviderSetting.SensitiveExternalIDs,
+ IsEmailVerified: identityProviderSetting.IsEmailVerified, FirstName: firstName, MiddleName: middleName, LastName: lastName, Email: email, Roles: roles,
+ Groups: groups, SystemSpecific: systemSpecific}
oidcParams := map[string]interface{}{}
oidcParams["id_token"] = token.IDToken
@@ -394,15 +418,13 @@ func (a *oidcAuthImpl) loadOidcTokenWithParams(params map[string]string, oidcCon
for k, v := range headers {
req.Header.Set(k, v)
}
-
- client := &http.Client{}
- resp, err := client.Do(req)
+ resp, err := a.client.Do(req)
if err != nil {
return nil, errors.WrapErrorAction(logutils.ActionSend, logutils.TypeRequest, nil, err)
}
defer resp.Body.Close()
- body, err := ioutil.ReadAll(resp.Body)
+ body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, errors.WrapErrorAction(logutils.ActionRead, logutils.TypeRequestBody, nil, err)
}
@@ -441,8 +463,7 @@ func (a *oidcAuthImpl) loadOidcUserInfo(token *oidcToken, url string) ([]byte, e
}
req.Header.Set("Authorization", fmt.Sprintf("%s %s", token.TokenType, token.AccessToken))
- client := &http.Client{}
- resp, err := client.Do(req)
+ resp, err := a.client.Do(req)
if err != nil {
return nil, errors.WrapErrorAction(logutils.ActionSend, logutils.TypeRequest, nil, err)
}
@@ -462,28 +483,21 @@ func (a *oidcAuthImpl) loadOidcUserInfo(token *oidcToken, url string) ([]byte, e
return body, nil
}
-func (a *oidcAuthImpl) getOidcAuthConfig(authType model.AuthType, appType model.ApplicationType) (*oidcAuthConfig, error) {
- errFields := &logutils.FieldArgs{"auth_type_id": authType.ID, "app_type_id": appType.ID}
+func (a *oidcAuthImpl) getOidcAuthConfig(authType model.AuthType, appTypeID string) (*oidcAuthConfig, error) {
+ errFields := &logutils.FieldArgs{"auth_type_id": authType.ID, "app_type_id": appTypeID}
identityProviderID, ok := authType.Params["identity_provider"].(string)
if !ok {
return nil, errors.ErrorData(logutils.StatusInvalid, "identity provider", errFields)
}
- appTypeID := appType.ID
authConfig, err := a.auth.getCachedIdentityProviderConfig(identityProviderID, appTypeID)
if err != nil {
return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeIdentityProviderConfig, errFields, err)
}
- configBytes, err := json.Marshal(authConfig.Config)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionMarshal, model.TypeIdentityProviderConfig, errFields, err)
- }
-
- var oidcConfig oidcAuthConfig
- err = json.Unmarshal(configBytes, &oidcConfig)
+ oidcConfig, err := utils.JSONConvert[oidcAuthConfig, map[string]interface{}](authConfig.Config)
if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, model.TypeIdentityProviderConfig, errFields, err)
+ return nil, errors.WrapErrorAction(logutils.ActionParse, model.TypeIdentityProviderConfig, errFields, err)
}
validate := validator.New()
@@ -492,30 +506,29 @@ func (a *oidcAuthImpl) getOidcAuthConfig(authType model.AuthType, appType model.
return nil, errors.WrapErrorAction(logutils.ActionValidate, model.TypeIdentityProviderConfig, errFields, err)
}
- return &oidcConfig, nil
+ return oidcConfig, nil
}
// --- Helper functions ---
// generatePkceChallenge generates and returns a PKCE code challenge and verifier
func generatePkceChallenge() (string, string, error) {
- codeVerifier, err := utils.GenerateRandomString(50)
- if err != nil {
- return "", "", errors.WrapErrorAction(logutils.ActionGenerate, "code verifier", nil, err)
- }
-
+ codeVerifier := utils.GenerateRandomString(50)
codeChallengeBytes, err := authutils.HashSha256([]byte(codeVerifier))
if err != nil {
return "", "", errors.WrapErrorAction(logutils.ActionCompute, "code verifier hash", nil, err)
}
- codeChallenge := base64.URLEncoding.EncodeToString(codeChallengeBytes)
+ codeChallenge := base64.RawURLEncoding.EncodeToString(codeChallengeBytes)
return codeChallenge, codeVerifier, nil
}
// initOidcAuth initializes and registers a new OIDC auth instance
func initOidcAuth(auth *Auth) (*oidcAuthImpl, error) {
- oidc := &oidcAuthImpl{auth: auth, authType: AuthTypeOidc}
+ client := &http.Client{
+ Transport: &transport{t: http.DefaultTransport, userAgent: "RokwireCore/" + auth.version},
+ }
+ oidc := &oidcAuthImpl{auth: auth, authType: AuthTypeOidc, client: client}
err := auth.registerExternalAuthType(oidc.authType, oidc)
if err != nil {
diff --git a/core/auth/auth_type_password.go b/core/auth/auth_type_password.go
new file mode 100644
index 000000000..e48eacd20
--- /dev/null
+++ b/core/auth/auth_type_password.go
@@ -0,0 +1,344 @@
+// Copyright 2023 Board of Trustees of the University of Illinois.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package auth
+
+import (
+ "core-building-block/core/model"
+ "core-building-block/utils"
+ "encoding/json"
+ "time"
+
+ "github.com/google/uuid"
+ "github.com/rokwire/logging-library-go/v2/errors"
+ "github.com/rokwire/logging-library-go/v2/logutils"
+ "golang.org/x/crypto/bcrypt"
+ "gopkg.in/go-playground/validator.v9"
+)
+
+const (
+ //AuthTypePassword password auth type
+ AuthTypePassword string = "password"
+
+ credentialKeyPassword string = "password"
+ typePasswordResetCode string = "password reset code"
+
+ typePasswordCreds logutils.MessageDataType = "password creds"
+ typePasswordParams logutils.MessageDataType = "password params"
+ typePasswordResetParams logutils.MessageDataType = "password reset params"
+)
+
+// passwordCreds represents the creds struct for password authentication
+type passwordCreds struct {
+ Password string `json:"password" validate:"required"`
+
+ ResetCode *string `json:"reset_code,omitempty"`
+ ResetExpiry *time.Time `json:"reset_expiry,omitempty"`
+}
+
+func (c *passwordCreds) toMap() (map[string]interface{}, error) {
+ if c == nil {
+ return nil, errors.ErrorData(logutils.StatusMissing, typePasswordCreds, nil)
+ }
+
+ credsMap, err := utils.JSONConvert[map[string]interface{}, passwordCreds](*c)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionParse, typePasswordCreds, nil, err)
+ }
+ if credsMap == nil {
+ return nil, errors.ErrorData(logutils.StatusInvalid, "password creds map", nil)
+ }
+ return *credsMap, nil
+}
+
+type passwordParams struct {
+ ConfirmPassword string `json:"confirm_password" validate:"required"`
+}
+
+type passwordResetParams struct {
+ NewPassword string `json:"new_password" validate:"required"`
+ passwordParams
+}
+
+// Password implementation of authType
+type passwordAuthImpl struct {
+ auth *Auth
+ authType string
+}
+
+func (a *passwordAuthImpl) signUp(identifierImpl identifierType, accountID *string, appOrg model.ApplicationOrganization, creds string, params string) (string, *model.AccountIdentifier, *model.Credential, error) {
+ credentials, err := a.parseCreds(creds, true)
+ if err != nil {
+ return "", nil, nil, errors.WrapErrorAction(logutils.ActionParse, typePasswordCreds, nil, err)
+ }
+
+ parameters, err := a.parseParams(params)
+ if err != nil {
+ return "", nil, nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typePasswordParams, nil, err)
+ }
+
+ if credentials.Password != parameters.ConfirmPassword {
+ return "", nil, nil, errors.ErrorData(logutils.StatusInvalid, "mismatching credentials", nil)
+ }
+
+ message := ""
+ var accountIdentifier *model.AccountIdentifier
+ if accountID == nil {
+ // we are not linking a password credential, so use the accountID generated for the identifier
+ message, accountIdentifier, err = identifierImpl.buildIdentifier(nil, appOrg.Application.Name)
+ if err != nil {
+ return "", nil, nil, errors.WrapErrorAction("building", "identifier", logutils.StringArgs(identifierImpl.getCode()), err)
+ }
+ }
+
+ credential, err := a.buildCredential(credentials.Password)
+ if err != nil {
+ return "", nil, nil, errors.WrapErrorAction("building", "password credentials", nil, err)
+ }
+
+ return message, accountIdentifier, credential, nil
+}
+
+func (a *passwordAuthImpl) signUpAdmin(identifierImpl identifierType, appOrg model.ApplicationOrganization, creds string) (map[string]interface{}, *model.AccountIdentifier, *model.Credential, error) {
+ credentials, err := a.parseCreds(creds, false)
+ if err != nil {
+ return nil, nil, nil, errors.WrapErrorAction(logutils.ActionParse, typePasswordCreds, nil, err)
+ }
+
+ if credentials.Password == "" {
+ credentials.Password = utils.GenerateRandomPassword(12)
+ }
+
+ _, accountIdentifier, err := identifierImpl.buildIdentifier(nil, appOrg.Application.Name)
+ if err != nil {
+ return nil, nil, nil, errors.WrapErrorAction("building", "identifier", logutils.StringArgs(identifierImpl.getCode()), err)
+ }
+
+ credential, err := a.buildCredential(credentials.Password)
+ if err != nil {
+ return nil, nil, nil, errors.WrapErrorAction("building", "password credentials", nil, err)
+ }
+
+ params := map[string]interface{}{"password": credentials.Password}
+ return params, accountIdentifier, credential, nil
+}
+
+func (a *passwordAuthImpl) forgotCredential(identifierImpl identifierType, credential *model.Credential, appOrg model.ApplicationOrganization) (map[string]interface{}, error) {
+ identifierChannel, _ := identifierImpl.(authCommunicationChannel)
+ if identifierChannel == nil {
+ return nil, errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, logutils.StringArgs(identifierImpl.getCode()))
+ }
+ if credential == nil {
+ return nil, errors.ErrorData(logutils.StatusMissing, model.TypeCredential, nil)
+ }
+
+ passwordCreds, err := a.mapToCreds(credential.Value)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionCast, "map to password creds", nil, err)
+ }
+
+ //TODO: turn length of reset code into a setting
+ resetCode := utils.GenerateRandomString(64)
+ hashedResetCode, err := bcrypt.GenerateFromPassword([]byte(resetCode), bcrypt.DefaultCost)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionGenerate, "reset code hash", nil, err)
+ }
+
+ hashedResetCodeStr := string(hashedResetCode)
+ resetExpiry := time.Now().UTC().Add(time.Hour * 24)
+ passwordCreds.ResetCode = &hashedResetCodeStr
+ passwordCreds.ResetExpiry = &resetExpiry
+
+ _, err = identifierChannel.sendCode(appOrg.Application.Name, resetCode, typePasswordResetCode, credential.ID)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionSend, "password reset code", nil, err)
+ }
+
+ credsMap, err := passwordCreds.toMap()
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionCast, "map from creds", nil, err)
+ }
+ return credsMap, nil
+}
+
+func (a *passwordAuthImpl) resetCredential(credential *model.Credential, resetCode *string, params string) (map[string]interface{}, error) {
+ if credential == nil {
+ return nil, errors.ErrorData(logutils.StatusMissing, model.TypeCredential, nil)
+ }
+ passwordCreds, err := a.mapToCreds(credential.Value)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionCast, "map to password creds", nil, err)
+ }
+
+ var resetData passwordResetParams
+ err = json.Unmarshal([]byte(params), &resetData)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typePasswordResetParams, nil, err)
+ }
+
+ if len(resetData.NewPassword) == 0 {
+ return nil, errors.ErrorData(logutils.StatusMissing, logutils.TypeString, logutils.StringArgs("new password"))
+ }
+ if len(resetData.ConfirmPassword) == 0 {
+ return nil, errors.ErrorData(logutils.StatusMissing, logutils.TypeString, logutils.StringArgs("confirm password"))
+ }
+ //check if the password matches with the confirm password one
+ if resetData.NewPassword != resetData.ConfirmPassword {
+ return nil, errors.ErrorData(logutils.StatusInvalid, "mismatching password reset fields", nil)
+ }
+
+ //reset password from link
+ if resetCode != nil {
+ if passwordCreds.ResetExpiry == nil || passwordCreds.ResetExpiry.Before(time.Now()) {
+ return nil, errors.ErrorData("expired", "reset expiration time", nil)
+ }
+ if passwordCreds.ResetCode == nil {
+ return nil, errors.ErrorData(logutils.StatusMissing, "stored reset code", nil)
+ }
+ err = bcrypt.CompareHashAndPassword([]byte(*passwordCreds.ResetCode), []byte(*resetCode))
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionValidate, "password reset code", nil, err)
+ }
+
+ //Update reset data
+ passwordCreds.ResetCode = nil
+ passwordCreds.ResetExpiry = nil
+ }
+
+ hashedPassword, err := bcrypt.GenerateFromPassword([]byte(resetData.NewPassword), bcrypt.DefaultCost)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionGenerate, "password hash", nil, err)
+ }
+
+ //Update password
+ passwordCreds.Password = string(hashedPassword)
+ credsMap, err := passwordCreds.toMap()
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionCast, "map from password creds", nil, err)
+ }
+
+ return credsMap, nil
+}
+
+func (a *passwordAuthImpl) checkCredentials(identifierImpl identifierType, accountID *string, aats []model.AccountAuthType, creds string, params string, appOrg model.ApplicationOrganization) (string, string, error) {
+ if len(aats) != 1 {
+ return "", "", errors.ErrorData(logutils.StatusInvalid, "account auth type list", &logutils.FieldArgs{"count": len(aats)})
+ }
+ if aats[0].Credential == nil {
+ return "", "", errors.ErrorData(logutils.StatusInvalid, model.TypeAccountAuthType, &logutils.FieldArgs{"id": aats[0].ID, "credential": nil})
+ }
+
+ storedCreds, err := a.mapToCreds(aats[0].Credential.Value)
+ if err != nil {
+ return "", "", errors.WrapErrorAction(logutils.ActionCast, "map to password creds", nil, err)
+ }
+
+ incomingCreds, err := a.parseCreds(creds, true)
+ if err != nil {
+ return "", "", errors.WrapErrorAction(logutils.ActionParse, typePasswordCreds, nil, err)
+ }
+
+ //compare stored and request passwords
+ err = bcrypt.CompareHashAndPassword([]byte(storedCreds.Password), []byte(incomingCreds.Password))
+ if err != nil {
+ return "", "", errors.WrapErrorAction(logutils.ActionValidate, model.TypeCredential, nil, err).SetStatus(utils.ErrorStatusInvalid)
+ }
+
+ return "", aats[0].Credential.ID, nil
+}
+
+func (a *passwordAuthImpl) withParams(params map[string]interface{}) (authType, error) {
+ return a, nil
+}
+
+func (a *passwordAuthImpl) requireIdentifierVerificationForSignIn() bool {
+ return true
+}
+
+func (a *passwordAuthImpl) allowMultiple() bool {
+ return false
+}
+
+// Helpers
+
+func (a *passwordAuthImpl) buildCredential(password string) (*model.Credential, error) {
+ //password hash
+ hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionGenerate, "password hash", nil, err)
+ }
+
+ credValue := &passwordCreds{Password: string(hashedPassword)}
+ credValueMap, err := credValue.toMap()
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionCast, "map from creds", nil, err)
+ }
+
+ credential := &model.Credential{ID: uuid.NewString(), Value: credValueMap, AuthType: model.AuthType{Code: a.authType}, DateCreated: time.Now().UTC()}
+ return credential, nil
+}
+
+func (a *passwordAuthImpl) parseCreds(creds string, validate bool) (*passwordCreds, error) {
+ var credential passwordCreds
+ err := json.Unmarshal([]byte(creds), &credential)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typePasswordCreds, nil, err)
+ }
+
+ if validate {
+ err = validator.New().Struct(credential)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionValidate, typePasswordCreds, nil, err)
+ }
+ }
+ return &credential, nil
+}
+
+func (a *passwordAuthImpl) parseParams(params string) (*passwordParams, error) {
+ var parameters passwordParams
+ err := json.Unmarshal([]byte(params), ¶meters)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typePasswordParams, nil, err)
+ }
+ err = validator.New().Struct(parameters)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionValidate, typePasswordParams, nil, err)
+ }
+ return ¶meters, nil
+}
+
+func (a *passwordAuthImpl) mapToCreds(credsMap map[string]interface{}) (*passwordCreds, error) {
+ creds, err := utils.JSONConvert[passwordCreds, map[string]interface{}](credsMap)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionParse, typePasswordCreds, nil, err)
+ }
+
+ err = validator.New().Struct(creds)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionValidate, typePasswordCreds, nil, err)
+ }
+ return creds, nil
+}
+
+// initPasswordAuth initializes and registers a new password auth instance
+func initPasswordAuth(auth *Auth) (*passwordAuthImpl, error) {
+ password := &passwordAuthImpl{auth: auth, authType: AuthTypePassword}
+
+ err := auth.registerAuthType(password.authType, password)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionRegister, model.TypeAuthType, nil, err)
+ }
+
+ return password, nil
+}
diff --git a/core/auth/auth_type_phone.go b/core/auth/auth_type_phone.go
deleted file mode 100644
index d663b86d6..000000000
--- a/core/auth/auth_type_phone.go
+++ /dev/null
@@ -1,319 +0,0 @@
-// Copyright 2022 Board of Trustees of the University of Illinois.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package auth
-
-import (
- "context"
- "core-building-block/core/model"
- "core-building-block/utils"
- "encoding/base64"
- "encoding/json"
- "io/ioutil"
- "net/http"
- "net/url"
- "regexp"
- "strings"
- "time"
-
- "github.com/rokwire/logging-library-go/v2/errors"
- "github.com/rokwire/logging-library-go/v2/logs"
- "github.com/rokwire/logging-library-go/v2/logutils"
- "gopkg.in/go-playground/validator.v9"
-)
-
-const (
- //AuthTypeTwilioPhone phone auth type
- AuthTypeTwilioPhone string = "twilio_phone"
-
- servicesPathPart = "https://verify.twilio.com/v2/Services"
- verificationsPathPart = "Verifications"
- verificationCheckPart = "VerificationCheck"
- typeVerifyServiceID logutils.MessageDataType = "phone verification service id"
- typeVerifyServiceToken logutils.MessageDataType = "phone verification service token"
- typeVerificationResponse logutils.MessageDataType = "phone verification response"
- typeVerificationStatus logutils.MessageDataType = "phone verification staus"
- typeVerificationSID logutils.MessageDataType = "phone verification sid"
- typePhoneCreds logutils.MessageDataType = "phone creds"
- typePhoneNumber logutils.MessageDataType = "E.164 phone number"
-)
-
-// Phone implementation of authType
-type twilioPhoneAuthImpl struct {
- auth *Auth
- authType string
- twilioAccountSID string
- twilioToken string
- twilioServiceSID string
-}
-
-type twilioPhoneCreds struct {
- Phone string `json:"phone" validate:"required"`
- Code string `json:"code"`
- // TODO: Password?
-}
-
-type verifyPhoneResponse struct {
- Status string `json:"status"`
- Payee interface{} `json:"payee"`
- DateUpdated time.Time `json:"date_updated"`
- AccountSid string `json:"account_sid"`
- To string `json:"to"`
- Amount interface{} `json:"amount"`
- Valid bool `json:"valid"`
- URL string `json:"url"`
- Sid string `json:"sid"`
- DateCreated time.Time `json:"date_created"`
- ServiceSid string `json:"service_sid"`
- Channel string `json:"channel"`
-}
-
-type checkStatusResponse struct {
- Sid string `json:"sid"`
- ServiceSid string `json:"service_sid"`
- AccountSid string `json:"account_sid"`
- To string `json:"to" validate:"required"`
- Channel string `json:"channel"`
- Status string `json:"status"`
- Amount interface{} `json:"amount"`
- Payee interface{} `json:"payee"`
- DateCreated time.Time `json:"date_created"`
- DateUpdated time.Time `json:"date_updated"`
-}
-
-func (a *twilioPhoneAuthImpl) checkRequestCreds(creds string) (*twilioPhoneCreds, error) {
- var requestCreds twilioPhoneCreds
- err := json.Unmarshal([]byte(creds), &requestCreds)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typePhoneCreds, nil, err)
- }
-
- validate := validator.New()
- err = validate.Struct(requestCreds)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionValidate, typePhoneCreds, nil, err)
- }
-
- phone := requestCreds.Phone
- validPhone := regexp.MustCompile(`^\+[1-9]\d{1,14}$`)
- if !validPhone.MatchString(phone) {
- return nil, errors.ErrorData(logutils.StatusInvalid, typePhoneNumber, &logutils.FieldArgs{"phone": phone})
- }
-
- return &requestCreds, nil
-}
-
-func (a *twilioPhoneAuthImpl) signUp(authType model.AuthType, appOrg model.ApplicationOrganization, creds string, params string, newCredentialID string, l *logs.Log) (string, map[string]interface{}, error) {
- requestCreds, err := a.checkRequestCreds(creds)
- if err != nil {
- return "", nil, err
- }
-
- message, err := a.handlePhoneVerify(requestCreds.Phone, *requestCreds, l)
- if err != nil {
- return "", nil, err
- }
-
- return message, nil, nil
-}
-
-func (a *twilioPhoneAuthImpl) signUpAdmin(authType model.AuthType, appOrg model.ApplicationOrganization, identifier string, password string, newCredentialID string) (map[string]interface{}, map[string]interface{}, error) {
- return nil, nil, nil
-}
-
-func (a *twilioPhoneAuthImpl) isCredentialVerified(credential *model.Credential, l *logs.Log) (*bool, *bool, error) {
- return nil, nil, nil
-}
-
-func (a *twilioPhoneAuthImpl) checkCredentials(accountAuthType model.AccountAuthType, creds string, l *logs.Log) (string, error) {
- requestCreds, err := a.checkRequestCreds(creds)
- if err != nil {
- return "", err
- }
-
- // existing user
- message, err := a.handlePhoneVerify(requestCreds.Phone, *requestCreds, l)
- if err != nil {
- return "", err
- }
-
- return message, nil
-}
-
-func (a *twilioPhoneAuthImpl) handlePhoneVerify(phone string, verificationCreds twilioPhoneCreds, l *logs.Log) (string, error) {
- if a.twilioAccountSID == "" {
- return "", errors.ErrorData(logutils.StatusMissing, typeVerifyServiceID, nil)
- }
-
- if a.twilioToken == "" {
- return "", errors.ErrorData(logutils.StatusMissing, typeVerifyServiceToken, nil)
- }
-
- data := url.Values{}
- data.Add("To", phone)
- if verificationCreds.Code != "" {
- // check verification
- data.Add("Code", verificationCreds.Code)
- return "", a.checkVerification(phone, data, l)
- }
-
- // start verification
- data.Add("Channel", "sms")
-
- message := ""
- err := a.startVerification(phone, data, l)
- if err == nil {
- message = "verification code sent successfully"
- }
- return message, err
-}
-
-func (a *twilioPhoneAuthImpl) startVerification(phone string, data url.Values, l *logs.Log) error {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
- body, err := makeRequest(ctx, "POST", servicesPathPart+"/"+a.twilioServiceSID+"/"+verificationsPathPart, data, a.twilioAccountSID, a.twilioToken)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionSend, logutils.TypeRequest, &logutils.FieldArgs{"verification params": data}, err)
- }
-
- var verifyResult verifyPhoneResponse
- err = json.Unmarshal(body, &verifyResult)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionUnmarshal, typeVerificationResponse, nil, err)
- }
-
- if verifyResult.To != phone {
- return errors.ErrorData(logutils.StatusInvalid, logutils.TypeString, &logutils.FieldArgs{"expected phone": phone, "actual phone": verifyResult.To})
- }
- if verifyResult.Status != "pending" {
- return errors.ErrorData(logutils.StatusInvalid, typeVerificationStatus, &logutils.FieldArgs{"expected pending, actual:": verifyResult.Status})
- }
- if verifyResult.Sid == "" {
- return errors.ErrorData(logutils.StatusMissing, typeVerificationSID, nil)
- }
-
- return nil
-}
-
-func (a *twilioPhoneAuthImpl) checkVerification(phone string, data url.Values, l *logs.Log) error {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- body, err := makeRequest(ctx, "POST", servicesPathPart+"/"+a.twilioServiceSID+"/"+verificationCheckPart, data, a.twilioAccountSID, a.twilioToken)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionSend, logutils.TypeRequest, nil, err)
- }
-
- var checkResponse checkStatusResponse
- err = json.Unmarshal(body, &checkResponse)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionUnmarshal, typeVerificationResponse, nil, err)
- }
-
- if checkResponse.To != phone {
- return errors.ErrorData(logutils.StatusInvalid, logutils.TypeString, &logutils.FieldArgs{"expected phone": phone, "actual phone": checkResponse.To})
- }
- if checkResponse.Status != "approved" {
- return errors.ErrorData(logutils.StatusInvalid, typeVerificationStatus, &logutils.FieldArgs{"expected approved, actual:": checkResponse.Status}).SetStatus(utils.ErrorStatusInvalid)
- }
-
- return nil
-}
-
-func makeRequest(ctx context.Context, method string, pathPart string, data url.Values, user string, token string) ([]byte, error) {
- client := &http.Client{}
- rb := new(strings.Reader)
- logAction := logutils.ActionSend
-
- if data != nil && (method == "POST" || method == "PUT") {
- rb = strings.NewReader(data.Encode())
- }
- if method == "GET" && data != nil {
- pathPart = pathPart + "?" + data.Encode()
- logAction = logutils.ActionRead
- }
-
- req, err := http.NewRequest(method, pathPart, rb)
- if err != nil {
- return nil, errors.WrapErrorAction(logAction, logutils.TypeRequest, &logutils.FieldArgs{"path": pathPart}, err)
- }
-
- if token != "" {
- req.Header.Add("Authorization", "Basic "+basicAuth(user, token))
- }
- req.Header.Add("Accept", "application/json")
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
-
- resp, err := client.Do(req)
- if err != nil {
- return nil, errors.WrapErrorAction(logAction, logutils.TypeRequest, nil, err)
- }
-
- defer resp.Body.Close()
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionRead, logutils.TypeRequestBody, nil, err)
- }
- if resp.StatusCode != 200 && resp.StatusCode != 201 {
- return nil, errors.ErrorData(logutils.StatusInvalid, logutils.TypeResponse, &logutils.FieldArgs{"status_code": resp.StatusCode, "error": string(body)})
- }
- return body, nil
-}
-
-func basicAuth(username, password string) string {
- auth := username + ":" + password
- return base64.StdEncoding.EncodeToString([]byte(auth))
-}
-
-func (a *twilioPhoneAuthImpl) getUserIdentifier(creds string) (string, error) {
- var requestCreds twilioPhoneCreds
- err := json.Unmarshal([]byte(creds), &requestCreds)
- if err != nil {
- return "", errors.WrapErrorAction(logutils.ActionUnmarshal, typePhoneCreds, nil, err)
- }
-
- return requestCreds.Phone, nil
-}
-
-func (a *twilioPhoneAuthImpl) verifyCredential(credential *model.Credential, verification string, l *logs.Log) (map[string]interface{}, error) {
- return nil, errors.New(logutils.Unimplemented)
-}
-
-func (a *twilioPhoneAuthImpl) sendVerifyCredential(credential *model.Credential, appName string, l *logs.Log) error {
- return nil
-}
-
-func (a *twilioPhoneAuthImpl) restartCredentialVerification(credential *model.Credential, appName string, l *logs.Log) error {
- return nil
-}
-
-func (a *twilioPhoneAuthImpl) resetCredential(credential *model.Credential, resetCode *string, params string, l *logs.Log) (map[string]interface{}, error) {
- return nil, nil
-}
-
-func (a *twilioPhoneAuthImpl) forgotCredential(credential *model.Credential, identifier string, appName string, l *logs.Log) (map[string]interface{}, error) {
- return nil, nil
-}
-
-// initPhoneAuth initializes and registers a new phone auth instance
-func initPhoneAuth(auth *Auth, twilioAccountSID string, twilioToken string, twilioServiceSID string) (*twilioPhoneAuthImpl, error) {
- phone := &twilioPhoneAuthImpl{auth: auth, authType: AuthTypeTwilioPhone, twilioAccountSID: twilioAccountSID, twilioToken: twilioToken, twilioServiceSID: twilioServiceSID}
-
- err := auth.registerAuthType(phone.authType, phone)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionRegister, model.TypeAuthType, nil, err)
- }
-
- return phone, nil
-}
diff --git a/core/auth/auth_type_signature.go b/core/auth/auth_type_signature.go
index b19d72538..2ffe023c1 100644
--- a/core/auth/auth_type_signature.go
+++ b/core/auth/auth_type_signature.go
@@ -18,7 +18,6 @@ import (
"core-building-block/core/model"
"github.com/rokwire/logging-library-go/v2/errors"
- "github.com/rokwire/logging-library-go/v2/logs"
"github.com/rokwire/logging-library-go/v2/logutils"
)
@@ -33,44 +32,36 @@ type signatureAuthImpl struct {
authType string
}
-func (a *signatureAuthImpl) signUp(authType model.AuthType, appOrg model.ApplicationOrganization, creds string, params string, newCredentialID string, l *logs.Log) (string, map[string]interface{}, error) {
- return "", nil, nil
+func (a *signatureAuthImpl) signUp(identifierImpl identifierType, accountID *string, appOrg model.ApplicationOrganization, creds string, params string) (string, *model.AccountIdentifier, *model.Credential, error) {
+ return "", nil, nil, nil
}
-func (a *signatureAuthImpl) signUpAdmin(authType model.AuthType, appOrg model.ApplicationOrganization, identifier string, password string, newCredentialID string) (map[string]interface{}, map[string]interface{}, error) {
- return nil, nil, nil
+func (a *signatureAuthImpl) signUpAdmin(identifierImpl identifierType, appOrg model.ApplicationOrganization, creds string) (map[string]interface{}, *model.AccountIdentifier, *model.Credential, error) {
+ return nil, nil, nil, nil
}
-func (a *signatureAuthImpl) getUserIdentifier(creds string) (string, error) {
- return "", nil
-}
-
-func (a *signatureAuthImpl) verifyCredential(credential *model.Credential, verification string, l *logs.Log) (map[string]interface{}, error) {
- return nil, errors.New(logutils.Unimplemented)
-}
-
-func (a *signatureAuthImpl) sendVerifyCredential(credential *model.Credential, appName string, l *logs.Log) error {
- return nil
+func (a *signatureAuthImpl) forgotCredential(identifierImpl identifierType, credential *model.Credential, appOrg model.ApplicationOrganization) (map[string]interface{}, error) {
+ return nil, nil
}
-func (a *signatureAuthImpl) restartCredentialVerification(credential *model.Credential, appName string, l *logs.Log) error {
- return nil
+func (a *signatureAuthImpl) resetCredential(credential *model.Credential, resetCode *string, params string) (map[string]interface{}, error) {
+ return nil, nil
}
-func (a *signatureAuthImpl) isCredentialVerified(credential *model.Credential, l *logs.Log) (*bool, *bool, error) {
- return nil, nil, nil
+func (a *signatureAuthImpl) checkCredentials(identifierImpl identifierType, accountID *string, aats []model.AccountAuthType, creds string, params string, appOrg model.ApplicationOrganization) (string, string, error) {
+ return "", "", nil
}
-func (a *signatureAuthImpl) checkCredentials(accountAuthType model.AccountAuthType, creds string, l *logs.Log) (string, error) {
- return "", nil
+func (a *signatureAuthImpl) withParams(params map[string]interface{}) (authType, error) {
+ return a, nil
}
-func (a *signatureAuthImpl) resetCredential(credential *model.Credential, resetCode *string, params string, l *logs.Log) (map[string]interface{}, error) {
- return nil, nil
+func (a *signatureAuthImpl) requireIdentifierVerificationForSignIn() bool {
+ return false
}
-func (a *signatureAuthImpl) forgotCredential(credential *model.Credential, identifier string, appName string, l *logs.Log) (map[string]interface{}, error) {
- return nil, nil
+func (a *signatureAuthImpl) allowMultiple() bool {
+ return false
}
// initSignatureAuth initializes and registers a new signature auth instance
diff --git a/core/auth/auth_type_username.go b/core/auth/auth_type_username.go
deleted file mode 100644
index 6cbbe6de4..000000000
--- a/core/auth/auth_type_username.go
+++ /dev/null
@@ -1,269 +0,0 @@
-// Copyright 2022 Board of Trustees of the University of Illinois.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package auth
-
-import (
- "core-building-block/core/model"
- "core-building-block/utils"
- "encoding/json"
-
- "github.com/rokwire/logging-library-go/v2/errors"
- "github.com/rokwire/logging-library-go/v2/logs"
- "github.com/rokwire/logging-library-go/v2/logutils"
- "golang.org/x/crypto/bcrypt"
-)
-
-const (
- authTypeUsername string = "username"
- typeUsernameCreds logutils.MessageDataType = "username creds"
- typeUsernameParams logutils.MessageDataType = "username params"
-)
-
-// Username implementation of authType
-type usernameAuthImpl struct {
- auth *Auth
- authType string
-}
-
-// userNameCreds represents the creds struct for username auth
-type usernameCreds struct {
- Username string `json:"username" bson:"username" validate:"required"`
- Password string `json:"password" bson:"password"`
-}
-
-func (a *usernameAuthImpl) signUp(authType model.AuthType, appOrg model.ApplicationOrganization, creds string, params string, newCredentialID string, l *logs.Log) (string, map[string]interface{}, error) {
- type signUpUsernameParams struct {
- ConfirmPassword string `json:"confirm_password"`
- }
-
- var sUsernameCreds usernameCreds
- err := json.Unmarshal([]byte(creds), &sUsernameCreds)
- if err != nil {
- return "", nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typeUsernameCreds, nil, err)
- }
-
- var sUsernameParams signUpUsernameParams
- err = json.Unmarshal([]byte(params), &sUsernameParams)
- if err != nil {
- return "", nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typeUsernameParams, nil, err)
- }
-
- username := sUsernameCreds.Username
- password := sUsernameCreds.Password
- confirmPassword := sUsernameParams.ConfirmPassword
- if len(username) == 0 {
- return "", nil, errors.ErrorData(logutils.StatusMissing, typeUsernameCreds, logutils.StringArgs("username"))
- }
- if len(password) == 0 {
- return "", nil, errors.ErrorData(logutils.StatusMissing, typeUsernameCreds, logutils.StringArgs("password"))
- }
-
- if len(confirmPassword) == 0 {
- return "", nil, errors.ErrorData(logutils.StatusMissing, typeUsernameParams, logutils.StringArgs("confirm_password"))
- }
- //check if the password matches with the confirm password one
- if password != confirmPassword {
- return "", nil, errors.ErrorData(logutils.StatusInvalid, "mismatching password fields", nil)
- }
-
- usernameCreds, err := a.buildCredentials(authType, appOrg.Application.Name, username, password, newCredentialID)
- if err != nil {
- return "", nil, errors.WrapErrorAction("building", "username credentials", nil, err)
- }
-
- return "", usernameCreds, nil
-}
-
-func (a *usernameAuthImpl) signUpAdmin(authType model.AuthType, appOrg model.ApplicationOrganization, identifier string, password string, newCredentialID string) (map[string]interface{}, map[string]interface{}, error) {
- if password == "" {
- password = utils.GenerateRandomPassword(12)
- }
-
- usernameCreds, err := a.buildCredentials(authType, appOrg.Application.Name, identifier, password, newCredentialID)
- if err != nil {
- return nil, nil, errors.WrapErrorAction("building", "username credentials", nil, err)
- }
-
- params := map[string]interface{}{"password": password}
- return params, usernameCreds, nil
-}
-
-func (a *usernameAuthImpl) buildCredentials(authType model.AuthType, appName string, username string, password string, credID string) (map[string]interface{}, error) {
-
- //password hash
- hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionGenerate, "password hash", nil, err)
- }
-
- usernameCredValue := usernameCreds{Username: username, Password: string(hashedPassword)}
-
- usernameCredValueMap, err := usernameCredsToMap(&usernameCredValue)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionCast, "map from username creds", nil, err)
- }
-
- return usernameCredValueMap, nil
-}
-
-func usernameCredsToMap(creds *usernameCreds) (map[string]interface{}, error) {
- credBytes, err := json.Marshal(creds)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionMarshal, typeUsernameCreds, nil, err)
- }
- var credsMap map[string]interface{}
- err = json.Unmarshal(credBytes, &credsMap)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, "map from username creds", nil, err)
- }
- return credsMap, nil
-}
-
-func (a *usernameAuthImpl) getUserIdentifier(creds string) (string, error) {
- var requestCreds usernameCreds
- err := json.Unmarshal([]byte(creds), &requestCreds)
- if err != nil {
- return "", errors.WrapErrorAction(logutils.ActionUnmarshal, typeUsernameCreds, nil, err)
- }
-
- return requestCreds.Username, nil
-}
-
-func (a *usernameAuthImpl) verifyCredential(credential *model.Credential, verification string, l *logs.Log) (map[string]interface{}, error) {
- return nil, errors.New(logutils.Unimplemented)
-}
-
-func (a *usernameAuthImpl) sendVerifyCredential(credential *model.Credential, appName string, l *logs.Log) error {
- return nil
-}
-
-func (a *usernameAuthImpl) restartCredentialVerification(credential *model.Credential, appName string, l *logs.Log) error {
- return nil
-}
-
-func (a *usernameAuthImpl) isCredentialVerified(credential *model.Credential, l *logs.Log) (*bool, *bool, error) {
- //TODO verification process for usernames
- verified := true
- expired := false
- return &verified, &expired, nil
-}
-
-func (a *usernameAuthImpl) checkCredentials(accountAuthType model.AccountAuthType, creds string, l *logs.Log) (string, error) {
- //get stored credential
- storedCreds, err := mapToUsernameCreds(accountAuthType.Credential.Value)
- if err != nil {
- return "", errors.WrapErrorAction(logutils.ActionCast, typeUsernameCreds, nil, err)
- }
-
- //get request credential
- type signInPasswordCred struct {
- Password string `json:"password"`
- }
- var sPasswordParams signInPasswordCred
- err = json.Unmarshal([]byte(creds), &sPasswordParams)
- if err != nil {
- return "", errors.WrapErrorAction(logutils.ActionUnmarshal, "sign in password creds", nil, err)
- }
- requestPassword := sPasswordParams.Password
-
- //compare stored and requests ones
- err = bcrypt.CompareHashAndPassword([]byte(storedCreds.Password), []byte(requestPassword))
- if err != nil {
- return "", errors.WrapErrorAction(logutils.ActionValidate, model.TypeCredential, nil, err).SetStatus(utils.ErrorStatusInvalid)
- }
-
- return "", nil
-}
-
-func mapToUsernameCreds(credsMap map[string]interface{}) (*usernameCreds, error) {
- credBytes, err := json.Marshal(credsMap)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionMarshal, typeUsernameCreds, nil, err)
- }
- var creds usernameCreds
- err = json.Unmarshal(credBytes, &creds)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typeUsernameCreds, nil, err)
- }
- return &creds, nil
-}
-
-func (a *usernameAuthImpl) resetCredential(credential *model.Credential, resetCode *string, params string, l *logs.Log) (map[string]interface{}, error) {
- //get the data from params
- type Params struct {
- NewPassword string `json:"new_password"`
- ConfirmPassword string `json:"confirm_password"`
- }
-
- var paramsData Params
- err := json.Unmarshal([]byte(params), ¶msData)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typeUsernameParams, nil, err)
- }
- newPassword := paramsData.NewPassword
- confirmPassword := paramsData.ConfirmPassword
-
- if len(newPassword) == 0 {
- return nil, errors.ErrorData(logutils.StatusMissing, logutils.TypeString, logutils.StringArgs("new_password"))
- }
- if len(confirmPassword) == 0 {
- return nil, errors.ErrorData(logutils.StatusMissing, logutils.TypeString, logutils.StringArgs("confirm_password"))
- }
- //check if the password matches with the confirm password one
- if newPassword != confirmPassword {
- return nil, errors.ErrorData(logutils.StatusInvalid, "mismatching password fields", nil)
- }
-
- credBytes, err := json.Marshal(credential.Value)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionMarshal, typeUsernameCreds, nil, err)
- }
-
- var creds *usernameCreds
- err = json.Unmarshal(credBytes, &creds)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typeUsernameCreds, nil, err)
- }
-
- hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionGenerate, "password hash", nil, err)
- }
-
- //Update verification data
- creds.Password = string(hashedPassword)
- credsMap, err := usernameCredsToMap(creds)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionCast, "map from username creds", nil, err)
- }
-
- return credsMap, nil
-}
-
-func (a *usernameAuthImpl) forgotCredential(credential *model.Credential, identifier string, appName string, l *logs.Log) (map[string]interface{}, error) {
- return nil, nil
-}
-
-// initUsernameAuth initializes and registers a new username auth instance
-func initUsernameAuth(auth *Auth) (*usernameAuthImpl, error) {
- username := &usernameAuthImpl{auth: auth, authType: authTypeUsername}
-
- err := auth.registerAuthType(username.authType, username)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionRegister, model.TypeAuthType, nil, err)
- }
-
- return username, nil
-}
diff --git a/core/auth/auth_type_webauthn.go b/core/auth/auth_type_webauthn.go
new file mode 100644
index 000000000..b65155d3d
--- /dev/null
+++ b/core/auth/auth_type_webauthn.go
@@ -0,0 +1,716 @@
+// Copyright 2022 Board of Trustees of the University of Illinois.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package auth
+
+import (
+ "bytes"
+ "core-building-block/core/model"
+ "core-building-block/driven/storage"
+ "core-building-block/utils"
+ "encoding/json"
+ "strings"
+ "time"
+
+ "github.com/go-webauthn/webauthn/protocol"
+ "github.com/go-webauthn/webauthn/webauthn"
+ "github.com/google/uuid"
+ "gopkg.in/go-playground/validator.v9"
+
+ "github.com/rokwire/logging-library-go/v2/errors"
+ "github.com/rokwire/logging-library-go/v2/logutils"
+)
+
+const (
+ //AuthTypeWebAuthn webauthn auth type
+ AuthTypeWebAuthn string = "webauthn"
+
+ credentialKeyResponse string = "response"
+ credentialKeyCredential string = "credential"
+
+ stateKeyChallenge string = "challenge"
+ stateKeySession string = "session"
+
+ typeWebAuthnCreds logutils.MessageDataType = "webauthn creds"
+ typeWebAuthnParams logutils.MessageDataType = "webauthn params"
+
+ rpDisplayNameKey string = "rp_display_name"
+)
+
+type webAuthnUser struct {
+ ID string
+ Name string
+ DisplayName string
+ Credentials []webauthn.Credential
+}
+
+// WebAuthnID unique user ID
+func (u webAuthnUser) WebAuthnID() []byte {
+ return []byte(u.ID)
+}
+
+// WebAuthnName unique human-readable identifier
+func (u webAuthnUser) WebAuthnName() string {
+ return u.Name
+}
+
+// WebAuthnDisplayName user display name (display purposes only)
+func (u webAuthnUser) WebAuthnDisplayName() string {
+ return u.DisplayName
+}
+
+// WebAuthnCredentials
+func (u webAuthnUser) WebAuthnCredentials() []webauthn.Credential {
+ return u.Credentials
+}
+
+// WebAuthnIcon deprecated
+func (u webAuthnUser) WebAuthnIcon() string {
+ return ""
+}
+
+// webauthnCreds represents the creds struct for webauthn authentication
+type webauthnCreds struct {
+ Credential *string `json:"credential,omitempty"`
+ Response *string `json:"response,omitempty"`
+}
+
+func (c *webauthnCreds) toMap() (map[string]interface{}, error) {
+ if c == nil {
+ return nil, errors.ErrorData(logutils.StatusMissing, typeWebAuthnCreds, nil)
+ }
+
+ credsMap, err := utils.JSONConvert[map[string]interface{}, webauthnCreds](*c)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionParse, typeWebAuthnCreds, nil, err)
+ }
+ if credsMap == nil {
+ return nil, errors.ErrorData(logutils.StatusInvalid, "webauthn creds map", nil)
+ }
+ return *credsMap, nil
+}
+
+type webauthnParams struct {
+ DisplayName *string `json:"display_name"`
+}
+
+// WebAuthn implementation of authType
+type webAuthnAuthImpl struct {
+ auth *Auth
+ authType string
+
+ config *webauthn.WebAuthn
+}
+
+func (a *webAuthnAuthImpl) signUp(identifierImpl identifierType, accountID *string, appOrg model.ApplicationOrganization, creds string, params string) (string, *model.AccountIdentifier, *model.Credential, error) {
+ parameters, err := a.parseParams(params)
+ if err != nil {
+ return "", nil, nil, errors.WrapErrorAction(logutils.ActionParse, typeWebAuthnParams, nil, err)
+ }
+
+ var user webAuthnUser
+ if identifierImpl != nil {
+ user.Name = identifierImpl.getIdentifier()
+ } else if parameters.DisplayName != nil {
+ user.Name = *parameters.DisplayName
+ }
+
+ var accountIdentifier *model.AccountIdentifier
+ if accountID != nil {
+ // we are linking a webauthn credential, so use the existing accountID
+ user.ID = *accountID
+ } else {
+ _, accountIdentifier, err = identifierImpl.buildIdentifier(nil, appOrg.Application.Name)
+ if err != nil {
+ return "", nil, nil, errors.WrapErrorAction("building", "identifier", logutils.StringArgs(identifierImpl.getCode()), err)
+ }
+
+ accountIdentifier.Verified = false
+ user.ID = accountIdentifier.Account.ID
+ }
+
+ if parameters.DisplayName != nil {
+ user.DisplayName = *parameters.DisplayName
+ } else if accountIdentifier != nil {
+ user.DisplayName = accountIdentifier.Identifier
+ }
+
+ message, err := a.beginRegistration(user, appOrg)
+ if err != nil {
+ return "", nil, nil, errors.WrapErrorAction(logutils.ActionStart, "webauthn registration", nil, err)
+ }
+
+ return message, accountIdentifier, nil, nil
+}
+
+func (a *webAuthnAuthImpl) signUpAdmin(identifierImpl identifierType, appOrg model.ApplicationOrganization, creds string) (map[string]interface{}, *model.AccountIdentifier, *model.Credential, error) {
+ return nil, nil, nil, errors.New(logutils.Unimplemented)
+}
+
+func (a *webAuthnAuthImpl) forgotCredential(identifierImpl identifierType, credential *model.Credential, appOrg model.ApplicationOrganization) (map[string]interface{}, error) {
+ //TODO: implement
+ return nil, errors.New(logutils.Unimplemented)
+}
+
+func (a *webAuthnAuthImpl) resetCredential(credential *model.Credential, resetCode *string, params string) (map[string]interface{}, error) {
+ //TODO: implement
+ return nil, errors.New(logutils.Unimplemented)
+}
+
+func (a *webAuthnAuthImpl) checkCredentials(identifierImpl identifierType, accountID *string, aats []model.AccountAuthType, creds string, params string, appOrg model.ApplicationOrganization) (string, string, error) {
+ incomingCreds, err := a.parseCreds(creds)
+ if err != nil {
+ return "", "", errors.WrapErrorAction(logutils.ActionParse, typeWebAuthnCreds, nil, err)
+ }
+
+ var user webauthn.User
+ if incomingCreds.Response == nil {
+ user, err = a.buildUser(accountID, aats)
+ if err != nil {
+ return "", "", errors.WrapErrorAction("building", "webauthn user", nil, err)
+ }
+
+ var optionData string
+ if user != nil && len(user.WebAuthnCredentials()) == 0 {
+ // attempting login with identifier and no credentials - need to restart registration instead
+ parameters, err := a.parseParams(params)
+ if err != nil {
+ return "", "", errors.WrapErrorAction(logutils.ActionParse, typeWebAuthnParams, nil, err)
+ }
+
+ newUser, ok := user.(webAuthnUser)
+ if !ok {
+ return "", "", errors.ErrorData(logutils.StatusInvalid, "webauthn user", nil)
+ }
+
+ if identifierImpl != nil {
+ newUser.Name = identifierImpl.getIdentifier()
+ } else if parameters.DisplayName != nil {
+ newUser.Name = *parameters.DisplayName
+ }
+
+ if parameters.DisplayName != nil {
+ newUser.DisplayName = *parameters.DisplayName
+ } else if identifierImpl != nil {
+ newUser.DisplayName = identifierImpl.getIdentifier()
+ }
+
+ optionData, err = a.beginRegistration(newUser, appOrg)
+ if err != nil {
+ return "", "", errors.WrapErrorAction(logutils.ActionStart, "webauthn registration", nil, err)
+ }
+ } else {
+ optionData, err = a.beginLogin(user, appOrg)
+ if err != nil {
+ return "", "", errors.WrapErrorAction("beginning", "webauthn login", nil, err)
+ }
+ }
+
+ return optionData, "", nil
+ }
+
+ // accountID will not be nil if linking or if account identifier has been verified during sign up
+ if accountID != nil {
+ user, err = a.buildUser(accountID, aats)
+ if err != nil {
+ return "", "", errors.WrapErrorAction("building", "webauthn user", nil, err)
+ }
+
+ // complete registration
+ if response, err := protocol.ParseCredentialCreationResponseBody(strings.NewReader(*incomingCreds.Response)); err == nil {
+ credID, err := a.completeRegistration(response, user, aats, appOrg)
+ if err != nil {
+ return "", "", err
+ }
+ return "", credID, nil
+ }
+ }
+
+ // either complete login with identifier or complete discoverable login without identifier
+ if response, err := protocol.ParseCredentialRequestResponseBody(strings.NewReader(*incomingCreds.Response)); err == nil {
+ if user != nil {
+ if len(response.Response.UserHandle) > 0 {
+ for _, aat := range aats {
+ // backwards compatibility: user handles (user IDs) used to be credential IDs
+ // check if the user handle matches any of the user's webauthn credential IDs
+ // if so, set the user handle equal to the user ID (now the account ID)
+ if aat.Credential != nil && bytes.Equal(response.Response.UserHandle, []byte(aat.Credential.ID)) {
+ response.Response.UserHandle = user.WebAuthnID()
+ break
+ }
+ }
+ }
+ }
+
+ credID, err := a.completeLogin(response, user, aats, appOrg)
+ return "", credID, err
+ }
+
+ // cannot parse response, so it is invalid
+ return "", "", errors.ErrorData(logutils.StatusInvalid, logutils.MessageDataType(credentialKeyResponse), nil)
+}
+
+func (a *webAuthnAuthImpl) withParams(params map[string]interface{}) (authType, error) {
+ rpDisplayName, _ := params["rp_display_name"].(string)
+ if rpDisplayName == "" {
+ rpDisplayName = rpDisplayNameKey
+ }
+ rpID, ok := params["rp_id"].(string)
+ if !ok {
+ return nil, errors.ErrorData(logutils.StatusInvalid, "supported auth type param", &logutils.FieldArgs{"param": "rp_id"})
+ }
+ rpOrigins, ok := params["rp_origins"].(string)
+ if !ok {
+ return nil, errors.ErrorData(logutils.StatusInvalid, "supported auth type param", &logutils.FieldArgs{"param": "rp_origins"})
+ }
+
+ requireResidentKey, _ := params["require_resident_key"].(bool)
+ residentKey, ok := params["resident_key"].(string)
+ if !ok {
+ residentKey = string(protocol.ResidentKeyRequirementRequired)
+ }
+ userVerification, ok := params["user_verification"].(string)
+ if !ok {
+ userVerification = string(protocol.VerificationRequired)
+ }
+ attestationPreference, ok := params["attestation_preference"].(string)
+ if !ok {
+ attestationPreference = string(protocol.PreferNoAttestation)
+ }
+ authenticatorAttachment, ok := params["authenticator_attachment"].(string)
+ if !ok {
+ authenticatorAttachment = string(protocol.Platform)
+ }
+
+ wconfig := &webauthn.Config{
+ RPDisplayName: rpDisplayName,
+ RPID: rpID, // Generally the FQDN for your site
+ RPOrigins: strings.Split(rpOrigins, ","), // The origin URLs allowed for WebAuthn requests
+ AttestationPreference: protocol.ConveyancePreference(attestationPreference),
+ AuthenticatorSelection: protocol.AuthenticatorSelection{
+ AuthenticatorAttachment: protocol.AuthenticatorAttachment(authenticatorAttachment),
+ RequireResidentKey: &requireResidentKey,
+ ResidentKey: protocol.ResidentKeyRequirement(residentKey),
+ UserVerification: protocol.UserVerificationRequirement(userVerification),
+ },
+ }
+
+ config, err := webauthn.New(wconfig)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionInitialize, model.TypeAuthType, nil, err)
+ }
+
+ return &webAuthnAuthImpl{auth: a.auth, authType: a.authType, config: config}, nil
+}
+
+func (a *webAuthnAuthImpl) requireIdentifierVerificationForSignIn() bool {
+ return true
+}
+
+func (a *webAuthnAuthImpl) allowMultiple() bool {
+ return true
+}
+
+// Helpers
+
+func (a *webAuthnAuthImpl) beginRegistration(user webauthn.User, appOrg model.ApplicationOrganization) (string, error) {
+ if a.config.Config.RPDisplayName == rpDisplayNameKey {
+ a.config.Config.RPDisplayName = appOrg.Application.Name
+ }
+ options, session, err := a.config.BeginRegistration(user)
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionRegister, model.TypeAccount, nil, err)
+ }
+
+ sessionData, err := json.Marshal(session)
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionMarshal, "session", nil, err)
+ }
+
+ state := map[string]interface{}{stateKeyChallenge: session.Challenge, stateKeySession: string(sessionData)}
+ accountID := string(user.WebAuthnID())
+ 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 {
+ return "", errors.WrapErrorAction(logutils.ActionCreate, model.TypeLoginState, nil, err)
+ }
+
+ optionData, err := json.Marshal(options)
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionMarshal, "creation options", nil, err)
+ }
+
+ return string(optionData), nil
+}
+
+func (a *webAuthnAuthImpl) completeRegistration(response *protocol.ParsedCredentialCreationData, user webauthn.User, aats []model.AccountAuthType, appOrg model.ApplicationOrganization) (string, error) {
+ if user == nil {
+ return "", errors.ErrorData(logutils.StatusMissing, model.TypeAccount, nil)
+ }
+
+ accountIDVal := string(user.WebAuthnID())
+ accountID := &accountIDVal
+
+ params := map[string]interface{}{
+ stateKeyChallenge: response.Response.CollectedClientData.Challenge,
+ }
+ loginState, err := a.auth.storage.FindLoginState(appOrg.Application.ID, appOrg.Organization.ID, accountID, params)
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionFind, model.TypeLoginState, nil, err)
+ }
+
+ session, err := a.parseWebAuthnSession(loginState.State)
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionParse, "session", nil, err)
+ }
+ if session == nil {
+ return "", errors.ErrorData(logutils.StatusMissing, "session", nil)
+ }
+
+ if a.config.Config.RPDisplayName == rpDisplayNameKey {
+ a.config.Config.RPDisplayName = appOrg.Application.Name
+ }
+ credential, err := a.config.CreateCredential(user, *session, response)
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionCreate, model.TypeCredential, nil, err)
+ }
+
+ credentialData, err := json.Marshal(credential)
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionMarshal, "credential", nil, err)
+ }
+
+ credentialStr := string(credentialData)
+ credValue := &webauthnCreds{Credential: &credentialStr}
+ credData, err := credValue.toMap()
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionCast, "map from webauthn creds", nil, err)
+ }
+
+ var accountAuthType *model.AccountAuthType
+ for i, aat := range aats {
+ if aat.Credential == nil {
+ accountAuthType = &aats[i]
+ break
+ }
+ }
+ if accountAuthType == nil {
+ return "", errors.ErrorData(logutils.StatusMissing, "account auth type without credential", &logutils.FieldArgs{"auth_type_code": a.authType, "account_id": accountIDVal})
+ }
+
+ credID := uuid.NewString()
+ transaction := func(context storage.TransactionContext) error {
+ //1. insert new credential
+ storeCred := &model.Credential{ID: credID, Value: credData, AccountsAuthTypes: []model.AccountAuthType{*accountAuthType},
+ AuthType: model.AuthType{Code: a.authType}, DateCreated: time.Now().UTC()}
+ err = a.auth.storage.InsertCredential(context, storeCred)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeCredential, nil, err)
+ }
+
+ //2. update the credential of the existing account auth type
+ accountAuthType.Credential = &model.Credential{ID: credID}
+ err = a.auth.storage.UpdateAccountAuthType(context, *accountAuthType)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeAccountAuthType, &logutils.FieldArgs{"id": accountAuthType.ID, "account_id": accountIDVal}, err)
+ }
+
+ return nil
+ }
+
+ err = a.auth.storage.PerformTransaction(transaction)
+ if err != nil {
+ return "", err
+ }
+
+ return credID, nil
+}
+
+func (a *webAuthnAuthImpl) beginLogin(user webauthn.User, appOrg model.ApplicationOrganization) (string, error) {
+ var options *protocol.CredentialAssertion
+ var session *webauthn.SessionData
+ var err error
+ var accountID *string
+ if a.config.Config.RPDisplayName == rpDisplayNameKey {
+ a.config.Config.RPDisplayName = appOrg.Application.Name
+ }
+ if user != nil {
+ options, session, err = a.config.BeginLogin(user)
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionStart, "login", nil, err)
+ }
+ accountIDVal := string(user.WebAuthnID())
+ accountID = &accountIDVal
+ } else {
+ // if no user, we can start a discoverable login
+ options, session, err = a.config.BeginDiscoverableLogin()
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionStart, "discoverable login", nil, err)
+ }
+ }
+
+ sessionData, err := json.Marshal(session)
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionMarshal, "session", nil, err)
+ }
+
+ state := map[string]interface{}{stateKeyChallenge: session.Challenge, stateKeySession: string(sessionData)}
+ 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 {
+ return "", errors.WrapErrorAction(logutils.ActionUpdate, model.TypeCredential, nil, err)
+ }
+
+ optionData, err := json.Marshal(options)
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionMarshal, "creation options", nil, err)
+ }
+
+ return string(optionData), nil
+}
+
+func (a *webAuthnAuthImpl) completeLogin(response *protocol.ParsedCredentialAssertionData, user webauthn.User, aats []model.AccountAuthType, appOrg model.ApplicationOrganization) (string, error) {
+ if a.config.Config.RPDisplayName == rpDisplayNameKey {
+ a.config.Config.RPDisplayName = appOrg.Application.Name
+ }
+
+ var accountID *string
+ if user != nil {
+ accountIDVal := string(user.WebAuthnID())
+ accountID = &accountIDVal
+ }
+
+ params := map[string]interface{}{
+ stateKeyChallenge: response.Response.CollectedClientData.Challenge,
+ }
+ loginState, err := a.auth.storage.FindLoginState(appOrg.Application.ID, appOrg.Organization.ID, accountID, params)
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionFind, model.TypeLoginState, nil, err)
+ }
+
+ session, err := a.parseWebAuthnSession(loginState.State)
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionParse, "session", nil, err)
+ }
+ if session == nil {
+ return "", errors.ErrorData(logutils.StatusMissing, "session", nil)
+ }
+
+ var credential *model.Credential
+ var updatedCred *webauthn.Credential
+ if user != nil {
+ updatedCred, err = a.config.ValidateLogin(user, *session, response)
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionValidate, "login", nil, err)
+ }
+
+ // find matching credential in provided list
+ for _, aat := range aats {
+ if aat.Credential != nil {
+ webAuthnCred, err := a.parseWebAuthnCredential(aat.Credential.Value)
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionParse, "webauthn credential", nil, err)
+ }
+
+ if webAuthnCred != nil && bytes.Equal(updatedCred.ID, webAuthnCred.ID) {
+ credential = aat.Credential
+ break
+ }
+ }
+ }
+ } else {
+ // if no user, we can validate a discoverable login
+ userDiscoverer := func(rawID, userHandle []byte) (webauthn.User, error) {
+ legacyUserHandle := false
+
+ // find account by userHandle (should match an account ID)
+ account, err := a.auth.storage.FindAccountByID(nil, string(userHandle))
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, &logutils.FieldArgs{"userHandle": string(userHandle)}, err)
+ }
+ if account == nil {
+ // backwards compatibility: user handles (user IDs) used to be credential IDs
+ // check if the user handle matches any of the user's webauthn credential IDs
+ account, err = a.auth.storage.FindAccountByCredentialID(nil, string(userHandle))
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, &logutils.FieldArgs{"userHandle": string(userHandle), "legacy": true}, err)
+ }
+
+ if account == nil {
+ return nil, errors.ErrorData(logutils.StatusMissing, model.TypeAccount, &logutils.FieldArgs{"userHandle": string(userHandle)})
+ }
+ legacyUserHandle = true
+ }
+
+ // find matching credential by rawId (should match a credential ID)
+ aats, err := a.auth.findAccountAuthTypesAndCredentials(account, model.SupportedAuthType{AuthType: model.AuthType{Code: a.authType}})
+ for _, aat := range aats {
+ if aat.Credential != nil {
+ webAuthnCred, err := a.parseWebAuthnCredential(aat.Credential.Value)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionParse, "webauthn credential", nil, err)
+ }
+
+ if webAuthnCred != nil && bytes.Equal(rawID, webAuthnCred.ID) {
+ credential = aat.Credential
+ userID := account.ID
+ if legacyUserHandle {
+ userID = credential.ID
+ }
+ return webAuthnUser{ID: userID, Credentials: []webauthn.Credential{*webAuthnCred}}, nil
+ }
+ }
+ }
+
+ return nil, errors.ErrorData(logutils.StatusMissing, "user", &logutils.FieldArgs{"userHandle": string(userHandle), "rawID": string(rawID)})
+ }
+
+ updatedCred, err = a.config.ValidateDiscoverableLogin(userDiscoverer, *session, response)
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionValidate, "discoverable login", nil, err)
+ }
+ }
+
+ credID := ""
+ if credential != nil {
+ credID = credential.ID
+ credentialData, err := json.Marshal(updatedCred)
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionMarshal, "credential", nil, err)
+ }
+
+ credential.Value[credentialKeyCredential] = string(credentialData)
+ err = a.auth.storage.UpdateCredentialValue(credID, credential.Value)
+ if err != nil {
+ return "", errors.WrapErrorAction(logutils.ActionUpdate, model.TypeCredential, nil, err)
+ }
+ }
+
+ return credID, nil
+}
+
+func (a *webAuthnAuthImpl) buildUser(accountID *string, aats []model.AccountAuthType) (webauthn.User, error) {
+ var user webauthn.User
+
+ userCredentials := make([]webauthn.Credential, 0)
+ for _, aat := range aats {
+ if aat.Credential != nil {
+ webAuthnCred, err := a.parseWebAuthnCredential(aat.Credential.Value)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionParse, "webauthn credential", nil, err)
+ }
+
+ // have at least one valid webauthn credential, so initiate login
+ if webAuthnCred != nil {
+ userCredentials = append(userCredentials, *webAuthnCred)
+ }
+ }
+ }
+
+ if accountID != nil { // otherwise identifier-less login
+ user = webAuthnUser{ID: *accountID, Credentials: userCredentials}
+ }
+
+ return user, nil
+}
+
+func (a *webAuthnAuthImpl) parseCreds(creds string) (*webauthnCreds, error) {
+ var credential webauthnCreds
+ err := json.Unmarshal([]byte(creds), &credential)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typeWebAuthnCreds, nil, err)
+ }
+ err = validator.New().Struct(credential)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionValidate, typeWebAuthnCreds, nil, err)
+ }
+ return &credential, nil
+}
+
+func (a *webAuthnAuthImpl) parseParams(params string) (*webauthnParams, error) {
+ var parameters webauthnParams
+ err := json.Unmarshal([]byte(params), ¶meters)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typeWebAuthnParams, nil, err)
+ }
+ err = validator.New().Struct(parameters)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionValidate, typeWebAuthnParams, nil, err)
+ }
+ return ¶meters, nil
+}
+
+func (a *webAuthnAuthImpl) mapToCreds(credsMap map[string]interface{}) (*webauthnCreds, error) {
+ creds, err := utils.JSONConvert[webauthnCreds, map[string]interface{}](credsMap)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionParse, typeWebAuthnCreds, nil, err)
+ }
+
+ err = validator.New().Struct(creds)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionValidate, typeWebAuthnCreds, nil, err)
+ }
+ return creds, nil
+}
+
+func (a *webAuthnAuthImpl) parseWebAuthnSession(credValue map[string]interface{}) (*webauthn.SessionData, error) {
+ if credValue[stateKeySession] == nil {
+ return nil, nil
+ }
+
+ sessionJSON, ok := credValue[stateKeySession].(string)
+ if !ok {
+ return nil, errors.ErrorData(logutils.StatusInvalid, "session", nil)
+ }
+
+ var session webauthn.SessionData
+ err := json.Unmarshal([]byte(sessionJSON), &session)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, "session", nil, err)
+ }
+
+ return &session, nil
+}
+
+func (a *webAuthnAuthImpl) parseWebAuthnCredential(credValue map[string]interface{}) (*webauthn.Credential, error) {
+ if credValue[credentialKeyCredential] == nil {
+ return nil, nil
+ }
+
+ credentialJSON, ok := credValue[credentialKeyCredential].(string)
+ if !ok {
+ return nil, errors.ErrorData(logutils.StatusInvalid, "credential", nil)
+ }
+
+ var credentialVal webauthn.Credential
+ err := json.Unmarshal([]byte(credentialJSON), &credentialVal)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, "credential", nil, err)
+ }
+
+ return &credentialVal, nil
+ // user.Credentials = []webauthn.Credential{credentialVal}
+}
+
+// initWebAuthnAuth initializes and registers a new webauthn auth instance
+func initWebAuthnAuth(auth *Auth) (*webAuthnAuthImpl, error) {
+ webauthn := &webAuthnAuthImpl{auth: auth, authType: AuthTypeWebAuthn}
+
+ err := auth.registerAuthType(webauthn.authType, webauthn)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionRegister, model.TypeAuthType, logutils.StringArgs(AuthTypeWebAuthn), err)
+ }
+
+ return webauthn, nil
+}
diff --git a/core/auth/identifier_type_email.go b/core/auth/identifier_type_email.go
new file mode 100644
index 000000000..02c090d08
--- /dev/null
+++ b/core/auth/identifier_type_email.go
@@ -0,0 +1,329 @@
+// Copyright 2022 Board of Trustees of the University of Illinois.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package auth
+
+import (
+ "core-building-block/core/model"
+ "core-building-block/utils"
+ "crypto/subtle"
+ "encoding/json"
+ "fmt"
+ "net/url"
+ "regexp"
+ "strings"
+ "time"
+
+ "github.com/google/uuid"
+ "github.com/rokwire/core-auth-library-go/v3/authutils"
+ "github.com/rokwire/logging-library-go/v2/errors"
+ "github.com/rokwire/logging-library-go/v2/logutils"
+ "gopkg.in/go-playground/validator.v9"
+)
+
+const (
+ //IdentifierTypeEmail email identifier type
+ IdentifierTypeEmail string = "email"
+
+ typeEmailIdentifier logutils.MessageDataType = "email identifier"
+)
+
+type emailIdentifier struct {
+ Email string `json:"email" validate:"required"`
+}
+
+// Email implementation of identifierType
+type emailIdentifierImpl struct {
+ auth *Auth
+ code string
+
+ identifier string
+}
+
+func (a *emailIdentifierImpl) getCode() string {
+ return a.code
+}
+
+func (a *emailIdentifierImpl) getIdentifier() string {
+ return a.identifier
+}
+
+func (a *emailIdentifierImpl) withIdentifier(creds string) (identifierType, error) {
+ var requestCreds emailIdentifier
+ err := json.Unmarshal([]byte(creds), &requestCreds)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typeEmailIdentifier, nil, err)
+ }
+
+ err = validator.New().Struct(requestCreds)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionValidate, typeEmailIdentifier, nil, err)
+ }
+
+ 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) {
+ return nil, errors.ErrorData(logutils.StatusInvalid, typeEmailIdentifier, &logutils.FieldArgs{"email": email})
+ }
+
+ return &emailIdentifierImpl{auth: a.auth, code: a.code, identifier: email}, nil
+}
+
+func (a *emailIdentifierImpl) buildIdentifier(accountID *string, appName string) (string, *model.AccountIdentifier, error) {
+ if a.identifier == "" {
+ return "", nil, errors.ErrorData(logutils.StatusMissing, "email identifier", nil)
+ }
+
+ accountIDStr := ""
+ if accountID != nil {
+ accountIDStr = *accountID
+ } else {
+ accountIDStr = uuid.NewString()
+ }
+
+ message := ""
+ accountIdentifier := model.AccountIdentifier{ID: uuid.NewString(), Code: a.code, Identifier: a.identifier, Verified: false,
+ Sensitive: true, Account: model.Account{ID: accountIDStr}, DateCreated: time.Now().UTC()}
+ sent, err := a.sendVerifyIdentifier(&accountIdentifier, appName)
+ if err != nil {
+ return "", nil, errors.WrapErrorAction(logutils.ActionSend, "identifier verification", nil, err)
+ }
+ if sent {
+ message = "verification code sent successfully"
+ }
+
+ return message, &accountIdentifier, nil
+}
+
+func (a *emailIdentifierImpl) maskIdentifier() (string, error) {
+ emailParts := strings.Split(a.identifier, "@")
+ if len(emailParts) != 2 {
+ return "", errors.ErrorData(logutils.StatusInvalid, typeEmailIdentifier, &logutils.FieldArgs{"identifier": a.identifier})
+ }
+
+ emailParts[0] = utils.GetLogValue(emailParts[0], 3) // mask all but the last 3 characters of the email prefix
+ return strings.Join(emailParts, "@"), nil
+}
+
+func (a *emailIdentifierImpl) requireVerificationForSignIn() bool {
+ return true
+}
+
+func (a *emailIdentifierImpl) checkVerified(accountIdentifier *model.AccountIdentifier, appName string) error {
+ verified := accountIdentifier.Verified
+ expired := accountIdentifier.VerificationExpiry == nil || accountIdentifier.VerificationExpiry.Before(time.Now())
+
+ if !verified {
+ //it is unverified
+ if !expired {
+ //not expired, just notify the client that it is "unverified"
+ return errors.ErrorData("unverified", model.TypeAccountIdentifier, nil).SetStatus(utils.ErrorStatusUnverified)
+ }
+
+ //restart identifier verification
+ err := a.restartIdentifierVerification(accountIdentifier, appName)
+ if err != nil {
+ return errors.WrapErrorAction("restarting", "identifier verification", nil, err)
+ }
+
+ err = a.auth.storage.UpdateAccountIdentifier(nil, *accountIdentifier)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeAccountIdentifier, nil, err)
+ }
+
+ //notify the client that it is unverified and verification is restarted
+ return errors.ErrorData("expired", "identifier verification", nil).SetStatus(utils.ErrorStatusVerificationExpired)
+ }
+
+ return nil
+}
+
+func (a *emailIdentifierImpl) allowMultiple() bool {
+ return true
+}
+
+// authCommunicationChannel interface
+
+func (a *emailIdentifierImpl) verifyIdentifier(accountIdentifier *model.AccountIdentifier, verification string) error {
+ if accountIdentifier == nil {
+ return errors.ErrorData(logutils.StatusMissing, model.TypeAccountIdentifier, nil)
+ }
+
+ if accountIdentifier.VerificationExpiry == nil || accountIdentifier.VerificationExpiry.Before(time.Now()) {
+ return errors.ErrorData("expired", "email verification code", nil)
+ }
+ if accountIdentifier.VerificationCode == nil {
+ return errors.ErrorData(logutils.StatusMissing, "email verification code", nil)
+ }
+ if subtle.ConstantTimeCompare([]byte(*accountIdentifier.VerificationCode), []byte(verification)) == 0 {
+ return errors.ErrorData(logutils.StatusInvalid, "email verification code", nil)
+ }
+
+ //Update verification data
+ now := time.Now().UTC()
+ accountIdentifier.Verified = true
+ accountIdentifier.VerificationCode = nil
+ accountIdentifier.VerificationExpiry = nil
+ accountIdentifier.DateUpdated = &now
+ return nil
+}
+
+func (a *emailIdentifierImpl) sendVerifyIdentifier(accountIdentifier *model.AccountIdentifier, appName string) (bool, error) {
+ if accountIdentifier == nil {
+ return false, errors.ErrorData(logutils.StatusMissing, model.TypeAccountIdentifier, nil)
+ }
+
+ //verification settings
+ verifyWaitTime, verifyExpiryTime, err := a.getVerificationSettings()
+ if err != nil {
+ return false, errors.WrapErrorAction(logutils.ActionGet, "email verification settings", nil, err)
+ }
+ if verifyWaitTime == nil || verifyExpiryTime == nil {
+ return false, nil
+ }
+
+ //Check if previous verification email was sent within the wait time if one was already sent
+ if accountIdentifier.VerificationExpiry != nil {
+ prevTime := accountIdentifier.VerificationExpiry.Add(time.Duration(-*verifyExpiryTime) * time.Hour)
+ if time.Now().UTC().Sub(prevTime) < time.Duration(*verifyWaitTime)*time.Second {
+ return false, errors.ErrorAction(logutils.ActionSend, "verification email", logutils.StringArgs("resend requested too soon"))
+ }
+ }
+
+ //verification code
+ //TODO: turn length of reset code into a setting
+ code := utils.GenerateRandomString(64)
+
+ //send verification email
+ if _, err = a.sendCode(appName, code, typeVerificationCode, accountIdentifier.ID); err != nil {
+ return false, errors.WrapErrorAction(logutils.ActionSend, "verification email", nil, err)
+ }
+
+ //Update verification data in credential value
+ now := time.Now().UTC()
+ newExpiry := now.Add(time.Hour * time.Duration(*verifyExpiryTime))
+ accountIdentifier.VerificationCode = &code
+ accountIdentifier.VerificationExpiry = &newExpiry
+ accountIdentifier.DateUpdated = &now
+ return true, nil
+}
+
+func (a *emailIdentifierImpl) restartIdentifierVerification(accountIdentifier *model.AccountIdentifier, appName string) error {
+ if accountIdentifier == nil {
+ return errors.ErrorData(logutils.StatusMissing, model.TypeAccountIdentifier, nil)
+ }
+
+ //Generate new verification code
+ newCode := utils.GenerateRandomString(64)
+
+ //send new verification code for future
+ if _, err := a.sendCode(appName, newCode, typeVerificationCode, accountIdentifier.ID); err != nil {
+ return errors.WrapErrorAction(logutils.ActionSend, "verification email", nil, err)
+ }
+ //update new verification data in credential value
+ expiry := time.Now().UTC().Add(time.Hour * 24)
+ accountIdentifier.VerificationCode = &newCode
+ accountIdentifier.VerificationExpiry = &expiry
+ return nil
+}
+
+func (a *emailIdentifierImpl) sendCode(appName string, code string, codeType string, itemID string) (string, error) {
+ if a.identifier == "" {
+ return "", errors.ErrorData(logutils.StatusMissing, typeEmailIdentifier, nil)
+ }
+
+ params := url.Values{}
+ params.Add("id", itemID)
+ params.Add("code", code)
+ switch codeType {
+ case typePasswordResetCode:
+ passwordResetLink := a.auth.host + fmt.Sprintf("/ui/credential/reset?%s", params.Encode())
+ subject := "Reset your password"
+ if appName != "" {
+ subject += " for " + appName
+ }
+ body := "Please click the link below to reset your password: " + passwordResetLink + "
If you did not request a password reset, please ignore this message."
+ return "", a.auth.emailer.Send(a.identifier, subject, body, nil)
+ case typeVerificationCode:
+ verificationLink := a.auth.host + fmt.Sprintf("/ui/identifier/verify?%s", params.Encode())
+ subject := "Verify your email address"
+ if appName != "" {
+ subject += " for " + appName
+ }
+ body := "Please click the link below to verify your email address: " + verificationLink + "
If you did not request this verification link, please ignore this message."
+ return "", a.auth.emailer.Send(a.identifier, subject, body, nil)
+ case typeAuthenticationCode:
+ subject := "Your authentication code"
+ body := "Please use the code " + code + " to login"
+ if appName != "" {
+ subject += " for " + appName
+ body += " to " + appName
+ }
+ body += ". If you did not request this authentication code, please ignore this message."
+ return "", a.auth.emailer.Send(a.identifier, subject, body, nil)
+ default:
+ return "", errors.ErrorData(logutils.StatusInvalid, "code type", logutils.StringArgs(codeType))
+ }
+}
+
+func (a *emailIdentifierImpl) requiresCodeGeneration() bool {
+ return true
+}
+
+// Helpers
+
+func (a *emailIdentifierImpl) getVerificationSettings() (*int, *int, error) {
+ // Time in seconds to wait before sending another auth code (default is 30)
+ verifyWaitTime := 30
+ // Time in hours before auth code expires (default is 24)
+ verifyExpiry := 24
+
+ config, err := a.auth.storage.FindConfig(model.ConfigTypeAuth, authutils.AllApps, authutils.AllOrgs)
+ if err != nil {
+ return nil, nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeConfig, &logutils.FieldArgs{"type": model.ConfigTypeAuth, "app_id": authutils.AllApps, "org_id": authutils.AllOrgs}, err)
+ }
+ if config != nil {
+ authConfigData, err := model.GetConfigData[model.AuthConfigData](*config)
+ if err != nil {
+ return nil, nil, errors.WrapErrorAction(logutils.ActionParse, model.TypeAuthConfigData, nil, err)
+ }
+
+ // Should email addresses be verified (default is true)
+ if authConfigData.EmailShouldVerify != nil && !*authConfigData.EmailShouldVerify {
+ return nil, nil, nil
+ }
+
+ if authConfigData.EmailVerifyWaitTime != nil {
+ verifyWaitTime = *authConfigData.EmailVerifyWaitTime
+ }
+
+ if authConfigData.EmailVerifyExpiry != nil {
+ verifyExpiry = *authConfigData.EmailVerifyExpiry
+ }
+ }
+
+ return &verifyWaitTime, &verifyExpiry, nil
+}
+
+// initEmailIdentifier initializes and registers a new email identifier instance
+func initEmailIdentifier(auth *Auth) (*emailIdentifierImpl, error) {
+ email := &emailIdentifierImpl{auth: auth, code: IdentifierTypeEmail}
+
+ err := auth.registerIdentifierType(email.code, email)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionRegister, typeIdentifierType, nil, err)
+ }
+
+ return email, nil
+}
diff --git a/core/auth/identifier_type_external.go b/core/auth/identifier_type_external.go
new file mode 100644
index 000000000..0a6ac4788
--- /dev/null
+++ b/core/auth/identifier_type_external.go
@@ -0,0 +1,98 @@
+// Copyright 2022 Board of Trustees of the University of Illinois.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package auth
+
+import (
+ "core-building-block/core/model"
+ "core-building-block/utils"
+ "encoding/json"
+ "strings"
+
+ "github.com/rokwire/logging-library-go/v2/errors"
+ "github.com/rokwire/logging-library-go/v2/logutils"
+)
+
+const (
+ //IdentifierTypeExternal external identifier type
+ IdentifierTypeExternal string = "external"
+
+ typeExternalIdentifier logutils.MessageDataType = "external identifier"
+)
+
+// External implementation of identifierType
+type externalIdentifierImpl struct {
+ auth *Auth
+ code string
+
+ identifier string
+}
+
+func (a *externalIdentifierImpl) getCode() string {
+ return a.code
+}
+
+func (a *externalIdentifierImpl) getIdentifier() string {
+ return a.identifier
+}
+
+func (a *externalIdentifierImpl) withIdentifier(creds string) (identifierType, error) {
+ var requestCreds map[string]string
+ err := json.Unmarshal([]byte(creds), &requestCreds)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typeExternalIdentifier, nil, err)
+ }
+
+ // these are keys that should NOT be treated as external identifier codes
+ excludeKeys := []string{IdentifierTypeEmail, IdentifierTypePhone, IdentifierTypeUsername, AuthTypePassword, AuthTypeCode, credentialKeyResponse}
+ for k, id := range requestCreds {
+ if !utils.Contains(excludeKeys, k) {
+ return &externalIdentifierImpl{auth: a.auth, code: k, identifier: strings.TrimSpace(id)}, nil
+ }
+ }
+
+ return nil, errors.ErrorData(logutils.StatusMissing, "external identifier", nil)
+}
+
+func (a *externalIdentifierImpl) buildIdentifier(accountID *string, appName string) (string, *model.AccountIdentifier, error) {
+ return "", nil, errors.ErrorData(logutils.StatusInvalid, typeIdentifierType, nil)
+}
+
+func (a *externalIdentifierImpl) maskIdentifier() (string, error) {
+ return a.identifier, nil
+}
+
+func (a *externalIdentifierImpl) requireVerificationForSignIn() bool {
+ return true
+}
+
+func (a *externalIdentifierImpl) checkVerified(accountIdentifier *model.AccountIdentifier, appName string) error {
+ return nil
+}
+
+func (a *externalIdentifierImpl) allowMultiple() bool {
+ return true
+}
+
+// initExternalIdentifier initializes and registers a new external identifier instance
+func initExternalIdentifier(auth *Auth) (*externalIdentifierImpl, error) {
+ external := &externalIdentifierImpl{auth: auth, code: IdentifierTypeExternal}
+
+ err := auth.registerIdentifierType(external.code, external)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionRegister, typeIdentifierType, nil, err)
+ }
+
+ return external, nil
+}
diff --git a/core/auth/identifier_type_phone.go b/core/auth/identifier_type_phone.go
new file mode 100644
index 000000000..9eca87e40
--- /dev/null
+++ b/core/auth/identifier_type_phone.go
@@ -0,0 +1,219 @@
+// Copyright 2022 Board of Trustees of the University of Illinois.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package auth
+
+import (
+ "core-building-block/core/model"
+ "core-building-block/utils"
+ "encoding/json"
+ "net/url"
+ "regexp"
+ "time"
+
+ "github.com/google/uuid"
+ "github.com/rokwire/logging-library-go/v2/errors"
+ "github.com/rokwire/logging-library-go/v2/logutils"
+ "gopkg.in/go-playground/validator.v9"
+)
+
+const (
+ //IdentifierTypePhone phone identifier type
+ IdentifierTypePhone string = "phone"
+
+ typePhoneIdentifier logutils.MessageDataType = "phone identifier"
+ typePhoneNumber logutils.MessageDataType = "E.164 phone number"
+)
+
+type phoneIdentifier struct {
+ Phone string `json:"phone" validate:"required"`
+}
+
+// Phone implementation of identifierType
+type phoneIdentifierImpl struct {
+ auth *Auth
+ code string
+
+ identifier string
+}
+
+func (a *phoneIdentifierImpl) getCode() string {
+ return a.code
+}
+
+func (a *phoneIdentifierImpl) getIdentifier() string {
+ return a.identifier
+}
+
+func (a *phoneIdentifierImpl) withIdentifier(creds string) (identifierType, error) {
+ var requestCreds phoneIdentifier
+ err := json.Unmarshal([]byte(creds), &requestCreds)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typePhoneIdentifier, nil, err)
+ }
+
+ err = validator.New().Struct(requestCreds)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionValidate, typePhoneIdentifier, nil, err)
+ }
+
+ validPhone := regexp.MustCompile(`^\+[1-9]\d{1,14}$`)
+ if !validPhone.MatchString(requestCreds.Phone) {
+ return nil, errors.ErrorData(logutils.StatusInvalid, typePhoneNumber, &logutils.FieldArgs{"phone": requestCreds.Phone})
+ }
+
+ return &phoneIdentifierImpl{auth: a.auth, code: a.code, identifier: requestCreds.Phone}, nil
+}
+
+func (a *phoneIdentifierImpl) buildIdentifier(accountID *string, appName string) (string, *model.AccountIdentifier, error) {
+ if a.identifier == "" {
+ return "", nil, errors.ErrorData(logutils.StatusMissing, "phone identifier", nil)
+ }
+
+ accountIDStr := ""
+ if accountID != nil {
+ accountIDStr = *accountID
+ } else {
+ accountIDStr = uuid.NewString()
+ }
+
+ message := ""
+ accountIdentifier := model.AccountIdentifier{ID: uuid.NewString(), Code: a.code, Identifier: a.identifier, Verified: false,
+ Sensitive: true, Account: model.Account{ID: accountIDStr}, DateCreated: time.Now().UTC()}
+ sent, err := a.sendVerifyIdentifier(&accountIdentifier, appName)
+ if err != nil {
+ return "", nil, errors.WrapErrorAction(logutils.ActionSend, "phone verification", nil, err)
+ }
+ if sent {
+ message = "verification code sent successfully"
+ }
+
+ return message, &accountIdentifier, nil
+}
+
+func (a *phoneIdentifierImpl) maskIdentifier() (string, error) {
+ return utils.GetLogValue(a.identifier, 4), nil // mask all but the last 4 phone digits
+}
+
+func (a *phoneIdentifierImpl) requireVerificationForSignIn() bool {
+ return false
+}
+
+func (a *phoneIdentifierImpl) checkVerified(accountIdentifier *model.AccountIdentifier, appName string) error {
+ verified := accountIdentifier.Verified
+ expired := accountIdentifier.VerificationExpiry == nil || accountIdentifier.VerificationExpiry.Before(time.Now())
+
+ if !verified {
+ //it is unverified
+ if !expired {
+ //not expired, just notify the client that it is "unverified"
+ return errors.ErrorData("unverified", model.TypeAccountIdentifier, nil).SetStatus(utils.ErrorStatusUnverified)
+ }
+
+ //restart identifier verification
+ err := a.restartIdentifierVerification(accountIdentifier, appName)
+ if err != nil {
+ return errors.WrapErrorAction("restarting", "identifier verification", nil, err)
+ }
+
+ //notify the client that it is unverified and verification is restarted
+ return errors.ErrorData("expired", "identifier verification", nil).SetStatus(utils.ErrorStatusVerificationExpired)
+ }
+
+ return nil
+}
+
+func (a *phoneIdentifierImpl) allowMultiple() bool {
+ return true
+}
+
+// authCommunicationChannel interface
+
+func (a *phoneIdentifierImpl) verifyIdentifier(accountIdentifier *model.AccountIdentifier, verification string) error {
+ _, err := a.sendCode("", verification, "", "")
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionVerify, "verification code", nil, err)
+ }
+
+ //TODO: do twilio/other phone verifiers have verification timeouts?
+ accountIdentifier.Verified = true
+ return nil
+}
+
+func (a *phoneIdentifierImpl) sendVerifyIdentifier(accountIdentifier *model.AccountIdentifier, appName string) (bool, error) {
+ if accountIdentifier == nil {
+ return false, errors.ErrorData(logutils.StatusMissing, model.TypeAccountIdentifier, nil)
+ }
+
+ //send verification code
+ if _, err := a.sendCode(appName, "", typeVerificationCode, accountIdentifier.ID); err != nil {
+ return false, errors.WrapErrorAction(logutils.ActionSend, "verification phone", nil, err)
+ }
+
+ //TODO: do twilio/other phone verifiers have verification timeouts?
+ return true, nil
+}
+
+func (a *phoneIdentifierImpl) restartIdentifierVerification(accountIdentifier *model.AccountIdentifier, appName string) error {
+ if accountIdentifier == nil {
+ return errors.ErrorData(logutils.StatusMissing, model.TypeAccountIdentifier, nil)
+ }
+
+ //send new verification code for future
+ if _, err := a.sendCode(appName, "", typeVerificationCode, accountIdentifier.ID); err != nil {
+ return errors.WrapErrorAction(logutils.ActionSend, "verification text", nil, err)
+ }
+
+ return nil
+}
+
+func (a *phoneIdentifierImpl) sendCode(appName string, code string, codeType string, itemID string) (string, error) {
+ if a.identifier == "" {
+ return "", errors.ErrorData(logutils.StatusMissing, typeEmailIdentifier, nil)
+ }
+
+ data := url.Values{}
+ data.Add("To", a.identifier)
+ if code != "" {
+ // check verification
+ data.Add("Code", code)
+ return "", a.auth.phoneVerifier.CheckVerification(a.identifier, data)
+ }
+
+ // start verification
+ data.Add("Channel", "sms")
+
+ message := ""
+ err := a.auth.phoneVerifier.StartVerification(a.identifier, data)
+ if err == nil {
+ message = "verification code sent successfully"
+ }
+ return message, err
+}
+
+func (a *phoneIdentifierImpl) requiresCodeGeneration() bool {
+ return false
+}
+
+// initPhoneIdentifier initializes and registers a new phone identifier instance
+func initPhoneIdentifier(auth *Auth) (*phoneIdentifierImpl, error) {
+ phone := &phoneIdentifierImpl{auth: auth, code: IdentifierTypePhone}
+
+ err := auth.registerIdentifierType(phone.code, phone)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionRegister, typeIdentifierType, nil, err)
+ }
+
+ return phone, nil
+}
diff --git a/core/auth/identifier_type_username.go b/core/auth/identifier_type_username.go
new file mode 100644
index 000000000..e508d5079
--- /dev/null
+++ b/core/auth/identifier_type_username.go
@@ -0,0 +1,124 @@
+// Copyright 2022 Board of Trustees of the University of Illinois.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package auth
+
+import (
+ "core-building-block/core/model"
+ "core-building-block/utils"
+ "encoding/json"
+ "strings"
+ "time"
+
+ "github.com/google/uuid"
+ "github.com/rokwire/logging-library-go/v2/errors"
+ "github.com/rokwire/logging-library-go/v2/logutils"
+ "gopkg.in/go-playground/validator.v9"
+)
+
+const (
+ //IdentifierTypeUsername username identifier type
+ IdentifierTypeUsername string = "username"
+
+ typeUsernameIdentifier logutils.MessageDataType = "username identifier"
+)
+
+type usernameIdentifier struct {
+ Username string `json:"username" validate:"required"`
+}
+
+// Username implementation of identifierType
+type usernameIdentifierImpl struct {
+ auth *Auth
+ code string
+
+ identifier string
+}
+
+func (a *usernameIdentifierImpl) getCode() string {
+ return a.code
+}
+
+func (a *usernameIdentifierImpl) getIdentifier() string {
+ return a.identifier
+}
+
+func (a *usernameIdentifierImpl) withIdentifier(creds string) (identifierType, error) {
+ var requestCreds usernameIdentifier
+ err := json.Unmarshal([]byte(creds), &requestCreds)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, typeUsernameIdentifier, nil, err)
+ }
+
+ err = validator.New().Struct(requestCreds)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionValidate, typeUsernameIdentifier, nil, err)
+ }
+
+ username := strings.TrimSpace(strings.ToLower(requestCreds.Username))
+
+ // some applications may append - to usernames to support cross-platform passkeys - we just want the raw username
+ platforms := []string{"android", "ios", "web"}
+ if usernameParts := strings.Split(username, "-"); len(usernameParts) > 1 && utils.Contains(platforms, usernameParts[len(usernameParts)-1]) {
+ username = strings.Join(usernameParts[:len(usernameParts)-1], "-")
+ }
+
+ return &usernameIdentifierImpl{auth: a.auth, code: a.code, identifier: username}, nil
+}
+
+func (a *usernameIdentifierImpl) buildIdentifier(accountID *string, appName string) (string, *model.AccountIdentifier, error) {
+ if a.identifier == "" {
+ return "", nil, errors.ErrorData(logutils.StatusMissing, "username identifier", nil)
+ }
+
+ accountIDStr := ""
+ if accountID != nil {
+ accountIDStr = *accountID
+ } else {
+ accountIDStr = uuid.NewString()
+ }
+
+ accountIdentifier := model.AccountIdentifier{ID: uuid.NewString(), Code: a.code, Identifier: a.identifier, Verified: true,
+ Account: model.Account{ID: accountIDStr}, DateCreated: time.Now().UTC()}
+
+ return "", &accountIdentifier, nil
+}
+
+func (a *usernameIdentifierImpl) maskIdentifier() (string, error) {
+ return a.identifier, nil
+}
+
+func (a *usernameIdentifierImpl) requireVerificationForSignIn() bool {
+ return true
+}
+
+func (a *usernameIdentifierImpl) checkVerified(accountIdentifier *model.AccountIdentifier, appName string) error {
+ return nil // return nil because username verification is not possible for now
+}
+
+func (a *usernameIdentifierImpl) allowMultiple() bool {
+ return false
+}
+
+// initUsernameIdentifier initializes and registers a new username identifier instance
+func initUsernameIdentifier(auth *Auth) (*usernameIdentifierImpl, error) {
+ username := &usernameIdentifierImpl{auth: auth, code: IdentifierTypeUsername}
+
+ err := auth.registerIdentifierType(username.code, username)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionRegister, typeIdentifierType, nil, err)
+ }
+
+ return username, nil
+}
diff --git a/core/auth/interfaces.go b/core/auth/interfaces.go
index 229dc44e8..ca913dd13 100644
--- a/core/auth/interfaces.go
+++ b/core/auth/interfaces.go
@@ -17,6 +17,7 @@ package auth
import (
"core-building-block/core/model"
"core-building-block/driven/storage"
+ "net/url"
"time"
"github.com/lestrrat-go/jwx/jwk"
@@ -26,52 +27,109 @@ import (
"github.com/rokwire/logging-library-go/v2/logs"
)
+// identifierType is the interface for auth identifiers that are not external to the system
+type identifierType interface {
+ //getType returns the identifier code
+ // Returns:
+ // identifierCode (string): identifier code
+ getCode() string
+
+ //getIdentifier returns the user identifier
+ // Returns:
+ // userIdentifier (string): User identifier
+ getIdentifier() string
+
+ //withIdentifier parses the credentials and copies the calling identifierType while caching the identifier
+ // Returns:
+ // identifierImpl (identifierType): Copy of calling identifierType with cached identifier
+ withIdentifier(creds string) (identifierType, error)
+
+ //buildIdentifier creates a new account identifier
+ // Returns:
+ // message (string): response message
+ // accountIdentifier (*model.AccountIdentifier): the new account identifier
+ buildIdentifier(accountID *string, appName string) (string, *model.AccountIdentifier, error)
+
+ // masks the cached identifier
+ maskIdentifier() (string, error)
+
+ // gives whether the identifier must be verified before sign-in is allowed
+ requireVerificationForSignIn() bool
+
+ // checks whether the given account identifier is verified, restarts verification if necessary and possible
+ checkVerified(accountIdentifier *model.AccountIdentifier, appName string) error
+
+ //allowMultiple says whether an account may have multiple identifiers of this type
+ // Returns:
+ // allowed (bool): whether mulitple identifier types are allowed
+ allowMultiple() bool
+}
+
+type authCommunicationChannel interface {
+ //verifies identifier (e.g., checks the verification code generated on email signup for email auth type)
+ verifyIdentifier(accountIdentifier *model.AccountIdentifier, verification string) error
+
+ //sends the verification code to the identifier
+ // Returns:
+ // sentCode (bool): whether the verification code was sent successfully
+ sendVerifyIdentifier(accountIdentifier *model.AccountIdentifier, appName string) (bool, error)
+
+ //restarts the identifier verification
+ restartIdentifierVerification(accountIdentifier *model.AccountIdentifier, appName string) error
+
+ //sendCode sends a verification code using the channel
+ // Returns:
+ // message (string): response message
+ sendCode(appName string, code string, codeType string, itemID string) (string, error)
+
+ //requiresCodeGeneration says whether a code needs to be generated by this service to send on the channel
+ // Returns:
+ // required (bool): whether codes need to be generated by the service
+ requiresCodeGeneration() bool
+}
+
// authType is the interface for authentication for auth types which are not external for the system(the users do not come from external system)
type authType interface {
//signUp applies sign up operation
// Returns:
// message (string): Success message if verification is required. If verification is not required, return ""
- // credentialValue (map): Credential value
- signUp(authType model.AuthType, appOrg model.ApplicationOrganization, creds string, params string, newCredentialID string, l *logs.Log) (string, map[string]interface{}, error)
+ // accountIdentifier (*model.AccountIdentifier): new account identifier
+ // credential (*model.Credential): new credential
+ signUp(identifierImpl identifierType, accountID *string, appOrg model.ApplicationOrganization, creds string, params string) (string, *model.AccountIdentifier, *model.Credential, error)
//signUpAdmin signs up a new admin user
// Returns:
- // password (string): newly generated password
- // credentialValue (map): Credential value
- signUpAdmin(authType model.AuthType, appOrg model.ApplicationOrganization, identifier string, password string, newCredentialID string) (map[string]interface{}, map[string]interface{}, error)
-
- //verifies credential (checks the verification code generated on email signup for email auth type)
- // Returns:
- // authTypeCreds (map[string]interface{}): Updated Credential.Value
- verifyCredential(credential *model.Credential, verification string, l *logs.Log) (map[string]interface{}, error)
+ // credentialParams (map): newly generated credential parameters
+ // accountIdentifier (*model.AccountIdentifier): new account identifier
+ // credential (*model.Credential): new credential
+ signUpAdmin(identifierImpl identifierType, appOrg model.ApplicationOrganization, creds string) (map[string]interface{}, *model.AccountIdentifier, *model.Credential, error)
- //sends the verification code to the identifier
- sendVerifyCredential(credential *model.Credential, appName string, l *logs.Log) error
-
- //restarts the credential verification
- restartCredentialVerification(credential *model.Credential, appName string, l *logs.Log) error
+ //apply forgot credential for the auth type (generates a reset password link with code and expiry and sends it to given identifier for email auth type)
+ forgotCredential(identifierImpl identifierType, credential *model.Credential, appOrg model.ApplicationOrganization) (map[string]interface{}, error)
//updates the value of the credential object with new value
// Returns:
// authTypeCreds (map[string]interface{}): Updated Credential.Value
- resetCredential(credential *model.Credential, resetCode *string, params string, l *logs.Log) (map[string]interface{}, error)
+ resetCredential(credential *model.Credential, resetCode *string, params string) (map[string]interface{}, error)
- //apply forgot credential for the auth type (generates a reset password link with code and expiry and sends it to given identifier for email auth type)
- forgotCredential(credential *model.Credential, identifier string, appName string, l *logs.Log) (map[string]interface{}, error)
-
- //getUserIdentifier parses the credentials and returns the user identifier
+ //checkCredential checks if the incoming credentials are valid for the stored credentials
// Returns:
- // userIdentifier (string): User identifier
- getUserIdentifier(creds string) (string, error)
+ // message (string): information required to complete login, if applicable
+ // credentialID (string): the ID of the credential used to validate the login
+ checkCredentials(identifierImpl identifierType, accountID *string, aats []model.AccountAuthType, creds string, params string, appOrg model.ApplicationOrganization) (string, string, error)
- //isCredentialVerified says if the credential is verified
+ //withParams parses the params and copies the calling authType while caching the params
// Returns:
- // verified (bool): is credential verified
- // expired (bool): is credential verification expired
- isCredentialVerified(credential *model.Credential, l *logs.Log) (*bool, *bool, error)
+ // authImpl (authType): Copy of calling authType with cached params
+ withParams(params map[string]interface{}) (authType, error)
+
+ // gives whether the identifier used with this auth type must be verified before sign-in is allowed
+ requireIdentifierVerificationForSignIn() bool
- //checkCredentials checks if the account credentials are valid for the account auth type
- checkCredentials(accountAuthType model.AccountAuthType, creds string, l *logs.Log) (string, error)
+ //allowMultiple says whether an account may have multiple auth types of this type
+ // Returns:
+ // allowed (bool): whether mulitple auth types are allowed
+ allowMultiple() bool
}
// externalAuthType is the interface for authentication for auth types which are external for the system(the users comes from external system).
@@ -123,7 +181,7 @@ type APIs interface {
// deviceType (string): "mobile" or "web" or "desktop" etc
// deviceOS (*string): Device OS
// deviceID (*string): Device ID
- // authenticationType (string): Name of the authentication method for provided creds (eg. "email", "username", "illinois_oidc")
+ // authenticationType (string): Name of the authentication method for provided creds (eg. "password", "code", "illinois_oidc")
// creds (string): Credentials/JSON encoded credential structure defined for the specified auth type
// apiKey (string): API key to validate the specified app
// appTypeIdentifier (string): identifier of the app type/client that the user is logging in from
@@ -132,10 +190,11 @@ type APIs interface {
// clientVersion(*string): Most recent client version
// profile (Profile): Account profile
// preferences (map): Account preferences
+ // accountIdentifierID (*string): UUID of account identifier, meant to be used after using SignInOptions
// admin (bool): Is this an admin login?
// l (*logs.Log): Log object pointer for request
// Returns:
- // Message (*string): message
+ // Response parameters (map): any messages or parameters to send in response when requiring identifier verification and/or NOT logging in the user
// Login session (*LoginSession): Signed ROKWIRE access token to be used to authorize future requests
// Access token (string): Signed ROKWIRE access token to be used to authorize future requests
// Refresh Token (string): Refresh token that can be sent to refresh the access token once it expires
@@ -145,7 +204,7 @@ type APIs interface {
// MFA types ([]model.MFAType): list of MFA types account is enrolled in
Login(ipAddress string, deviceType string, deviceOS *string, deviceID *string, authenticationType string, creds string, apiKey string,
appTypeIdentifier string, orgID string, params string, clientVersion *string, profile model.Profile, privacy model.Privacy, preferences map[string]interface{},
- username string, admin bool, l *logs.Log) (*string, *model.LoginSession, []model.MFAType, error)
+ accountIdentifierID *string, admin bool, l *logs.Log) (map[string]interface{}, *model.LoginSession, []model.MFAType, error)
//Logout logouts an account from app/org
// Input:
@@ -153,40 +212,45 @@ type APIs interface {
Logout(appID string, orgID string, currentAccountID string, sessionID string, allSessions bool, l *logs.Log) error
//AccountExists checks if a user is already registered
- //The authentication method must be one of the supported for the application.
// Input:
- // authenticationType (string): Name of the authentication method for provided creds (eg. "email", "username", "illinois_oidc")
// userIdentifier (string): User identifier for the specified auth type
// apiKey (string): API key to validate the specified app
// appTypeIdentifier (string): identifier of the app type/client that the user is logging in from
// orgID (string): ID of the organization that the user is logging in
// Returns:
// accountExisted (bool): valid when error is nil
- AccountExists(authenticationType string, userIdentifier string, apiKey string, appTypeIdentifier string, orgID string) (bool, error)
+ AccountExists(identifierJSON string, apiKey string, appTypeIdentifier string, orgID string, authenticationType *string, userIdentifier *string) (bool, error)
//CanSignIn checks if a user can sign in
- //The authentication method must be one of the supported for the application.
// Input:
- // authenticationType (string): Name of the authentication method for provided creds (eg. "email", "username", "illinois_oidc")
// userIdentifier (string): User identifier for the specified auth type
// apiKey (string): API key to validate the specified app
// appTypeIdentifier (string): identifier of the app type/client being used
// orgID (string): ID of the organization being used
// Returns:
// canSignIn (bool): valid when error is nil
- CanSignIn(authenticationType string, userIdentifier string, apiKey string, appTypeIdentifier string, orgID string) (bool, error)
+ CanSignIn(identifierJSON string, apiKey string, appTypeIdentifier string, orgID string, authenticationType *string, userIdentifier *string) (bool, error)
//CanLink checks if a user can link a new auth type
- //The authentication method must be one of the supported for the application.
// Input:
- // authenticationType (string): Name of the authentication method for provided creds (eg. "email", "username", "illinois_oidc")
// userIdentifier (string): User identifier for the specified auth type
// apiKey (string): API key to validate the specified app
// appTypeIdentifier (string): identifier of the app type/client being used
// orgID (string): ID of the organization being used
// Returns:
// canLink (bool): valid when error is nil
- CanLink(authenticationType string, userIdentifier string, apiKey string, appTypeIdentifier string, orgID string) (bool, error)
+ CanLink(identifierJSON string, apiKey string, appTypeIdentifier string, orgID string, authenticationType *string, userIdentifier *string) (bool, error)
+
+ //SignInOptions returns the identifiers and auth types that may be used to sign in to an account
+ // Input:
+ // userIdentifier (string): User identifier for the specified auth type
+ // apiKey (string): API key to validate the specified app
+ // appTypeIdentifier (string): identifier of the app type/client being used
+ // orgID (string): ID of the organization being used
+ // Returns:
+ // identifiers ([]model.AccountIdentifier): account identifiers that may be used for sign-in
+ // authTypes ([]model.AccountAuthType): account auth types that may be used for sign-in
+ SignInOptions(identifierJSON string, apiKey string, appTypeIdentifier string, orgID string, authenticationType *string, userIdentifier *string, l *logs.Log) ([]model.AccountIdentifier, []model.AccountAuthType, error)
//Refresh refreshes an access token using a refresh token
// Input:
@@ -203,7 +267,7 @@ type APIs interface {
//GetLoginURL returns a pre-formatted login url for SSO providers
// Input:
- // authType (string): Name of the authentication method for provided creds (eg. "email", "username", "illinois_oidc")
+ // authType (string): Name of the authentication method for provided creds (eg. "illinois_oidc")
// appTypeIdentifier (string): Identifier of the app type/client that the user is logging in from
// orgID (string): ID of the organization that the user is logging in
// redirectURI (string): Registered redirect URI where client will receive response
@@ -234,22 +298,22 @@ type APIs interface {
LoginMFA(apiKey string, accountID string, sessionID string, identifier string, mfaType string, mfaCode string, state string, l *logs.Log) (*string, *model.LoginSession, error)
//CreateAdminAccount creates an account for a new admin user
- CreateAdminAccount(authenticationType string, appID string, orgID string, identifier string, profile model.Profile, privacy model.Privacy, username string, permissions []string,
+ CreateAdminAccount(authenticationType string, appID string, orgID string, identifierJSON string, profile model.Profile, privacy model.Privacy, permissions []string,
roleIDs []string, groupIDs []string, scopes []string, creatorPermissions []string, clientVersion *string, l *logs.Log) (*model.Account, map[string]interface{}, error)
//UpdateAdminAccount updates an existing user's account with new permissions, roles, and groups
- UpdateAdminAccount(authenticationType string, appID string, orgID string, identifier string, permissions []string, roleIDs []string,
+ UpdateAdminAccount(authenticationType string, appID string, orgID string, identifierJSON string, permissions []string, roleIDs []string,
groupIDs []string, scopes []string, updaterPermissions []string, l *logs.Log) (*model.Account, map[string]interface{}, error)
//CreateAnonymousAccount creates a new anonymous account
CreateAnonymousAccount(context storage.TransactionContext, appID string, orgID string, anonymousID string, preferences map[string]interface{},
systemConfigs map[string]interface{}, skipExistsCheck bool, l *logs.Log) (*model.Account, error)
- //VerifyCredential verifies credential (checks the verification code in the credentials collection)
- VerifyCredential(id string, verification string, l *logs.Log) error
+ //VerifyIdentifier verifies credential (checks the verification code in the credentials collection)
+ VerifyIdentifier(id string, verification string, l *logs.Log) (*model.AccountIdentifier, error)
- //SendVerifyCredential sends verification code to identifier
- SendVerifyCredential(authenticationType string, appTypeIdentifier string, orgID string, apiKey string, identifier string, l *logs.Log) error
+ //SendVerifyIdentifier sends verification code to identifier
+ SendVerifyIdentifier(appTypeIdentifier string, orgID string, apiKey string, identifierJSON string, l *logs.Log) error
//UpdateCredential updates the credential object with the new value
// Input:
@@ -262,14 +326,14 @@ type APIs interface {
//ForgotCredential initiate forgot credential process (generates a reset link and sends to the given identifier for email auth type)
// Input:
- // authenticationType (string): Name of the authentication method for provided creds (eg. "email", "username", "illinois_oidc")
- // identifier: identifier of the account auth type
+ // authenticationType (string): Name of the authentication method for provided creds (eg. "password")
+ // identifierJSON (string): JSON string of the user's identifier and the identifier code
// appTypeIdentifier (string): Identifier of the app type/client that the user is logging in from
// orgID (string): ID of the organization that the user is logging in
// apiKey (string): API key to validate the specified app
// Returns:
// error: if any
- ForgotCredential(authenticationType string, appTypeIdentifier string, orgID string, apiKey string, identifier string, l *logs.Log) error
+ ForgotCredential(authenticationType string, identifierJSON string, appTypeIdentifier string, orgID string, apiKey string, l *logs.Log) error
//ResetForgotCredential resets forgot credential
// Input:
@@ -367,8 +431,8 @@ type APIs interface {
//The authentication method must be one of the supported for the application.
// Input:
// accountID (string): ID of the account to link the creds to
- // authenticationType (string): Name of the authentication method for provided creds (eg. "email", "username", "illinois_oidc")
- // appTypeIdentifier (string): identifier of the app type/client that the user is logging in from
+ // authenticationType (string): Name of the authentication method for provided creds (eg. "password", "webauthn", "illinois_oidc")
+ // appTypeIdentifier (string): Identifier of the app type/client that the user is logging in from
// creds (string): Credentials/JSON encoded credential structure defined for the specified auth type
// params (string): JSON encoded params defined by specified auth type
// l (*logs.Log): Log object pointer for request
@@ -381,13 +445,17 @@ type APIs interface {
//The authentication method must be one of the supported for the application.
// Input:
// accountID (string): ID of the account to unlink creds from
- // authenticationType (string): Name of the authentication method of account auth type to unlink
- // appTypeIdentifier (string): Identifier of the app type/client that the user is logging in from
- // identifier (string): Identifier of account auth type to unlink
+ // accountAuthTypeID (*string): Account auth type to unlink
+ // authenticationType (*string): Name of the authentication method of account auth type to unlink
+ // identifier (*string): Identifier to unlink
// l (*logs.Log): Log object pointer for request
// Returns:
// account (*model.Account): account data after the operation
- UnlinkAccountAuthType(accountID string, authenticationType string, appTypeIdentifier string, identifier string, l *logs.Log) (*model.Account, error)
+ UnlinkAccountAuthType(accountID string, accountAuthTypeID *string, authenticationType *string, identifier *string, admin bool, l *logs.Log) (*model.Account, error)
+
+ LinkAccountIdentifier(accountID string, identifierJSON string, admin bool, l *logs.Log) (*string, *model.Account, error)
+
+ UnlinkAccountIdentifier(accountID string, accountIdentifierID string, admin bool, l *logs.Log) (*model.Account, error)
//InitializeSystemAccount initializes the first system account
InitializeSystemAccount(context storage.TransactionContext, authType model.AuthType, appOrg model.ApplicationOrganization, allSystemPermission string, email string, password string, clientVersion string, l *logs.Log) (string, error)
@@ -456,6 +524,9 @@ type Storage interface {
PerformTransaction(func(context storage.TransactionContext) error) error
+ //Configs
+ FindConfig(configType string, appID string, orgID string) (*model.Config, error)
+
//AuthTypes
FindAuthType(codeOrID string) (*model.AuthType, error)
@@ -467,7 +538,6 @@ type Storage interface {
UpdateLoginSession(context storage.TransactionContext, loginSession model.LoginSession) error
DeleteLoginSession(context storage.TransactionContext, id string) error
DeleteLoginSessionsByIDs(context storage.TransactionContext, ids []string) error
- DeleteLoginSessionsByAccountAuthTypeID(context storage.TransactionContext, id string) error
DeleteLoginSessionsByIdentifier(context storage.TransactionContext, identifier string) error
//LoginsSessions - predefined queries for manage deletion logic
@@ -475,8 +545,12 @@ type Storage interface {
FindSessionsLazy(appID string, orgID string) ([]model.LoginSession, error)
///
+ //LoginStates
+ FindLoginState(appID string, orgID string, accountID *string, stateParams map[string]interface{}) (*model.LoginState, error)
+ InsertLoginState(loginState model.LoginState) error
+
//Accounts
- FindAccount(context storage.TransactionContext, appOrgID string, authTypeID string, accountAuthTypeIdentifier string) (*model.Account, error)
+ FindAccount(context storage.TransactionContext, appOrgID string, code string, identifier string) (*model.Account, error)
FindAccountByID(context storage.TransactionContext, id string) (*model.Account, error)
FindAccountsByUsername(context storage.TransactionContext, appOrg *model.ApplicationOrganization, username string) ([]model.Account, error)
InsertAccount(context storage.TransactionContext, account model.Account) (*model.Account, error)
@@ -486,7 +560,7 @@ type Storage interface {
//Profiles
UpdateAccountProfile(context storage.TransactionContext, profile model.Profile) error
- FindAccountProfiles(appID string, authTypeID string, accountAuthTypeIdentifier string) ([]model.Profile, error)
+ FindAccountProfiles(appID string, accountIdentifier string) ([]model.Profile, error)
//ServiceAccounts
FindServiceAccount(context storage.TransactionContext, accountID string, appID string, orgID string) (*model.ServiceAccount, error)
@@ -502,13 +576,18 @@ type Storage interface {
//AccountAuthTypes
FindAccountByAuthTypeID(context storage.TransactionContext, id string) (*model.Account, error)
- InsertAccountAuthType(item model.AccountAuthType) error
- UpdateAccountAuthType(item model.AccountAuthType) error
+ FindAccountByCredentialID(context storage.TransactionContext, id string) (*model.Account, error)
+ InsertAccountAuthType(context storage.TransactionContext, item model.AccountAuthType) error
+ UpdateAccountAuthType(context storage.TransactionContext, item model.AccountAuthType) error
DeleteAccountAuthType(context storage.TransactionContext, item model.AccountAuthType) error
- //ExternalIDs
- UpdateAccountExternalIDs(accountID string, externalIDs map[string]string) error
- UpdateLoginSessionExternalIDs(accountID string, externalIDs map[string]string) error
+ //AccountIdentifiers
+ FindAccountByIdentifierID(context storage.TransactionContext, id string) (*model.Account, error)
+ InsertAccountIdentifier(context storage.TransactionContext, item model.AccountIdentifier) error
+ UpdateAccountIdentifier(context storage.TransactionContext, item model.AccountIdentifier) error
+ UpdateAccountIdentifiers(context storage.TransactionContext, accountID string, items []model.AccountIdentifier) error
+ DeleteAccountIdentifier(context storage.TransactionContext, item model.AccountIdentifier) error
+ DeleteExternalAccountIdentifiers(context storage.TransactionContext, aat model.AccountAuthType) error
//Applications
FindApplication(context storage.TransactionContext, ID string) (*model.Application, error)
@@ -519,6 +598,7 @@ type Storage interface {
//Credentials
InsertCredential(context storage.TransactionContext, creds *model.Credential) error
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
DeleteCredential(context storage.TransactionContext, ID string) error
@@ -601,3 +681,9 @@ type IdentityBuildingBlock interface {
type Emailer interface {
Send(toEmail string, subject string, body string, attachmentFilename *string) error
}
+
+// PhoneVerifier is used by core to verify phone numbers
+type PhoneVerifier interface {
+ StartVerification(phone string, data url.Values) error
+ CheckVerification(phone string, data url.Values) error
+}
diff --git a/core/auth/mfa_recovery.go b/core/auth/mfa_recovery.go
index e00cd9c45..2daac5c49 100644
--- a/core/auth/mfa_recovery.go
+++ b/core/auth/mfa_recovery.go
@@ -18,7 +18,6 @@ import (
"core-building-block/core/model"
"core-building-block/driven/storage"
"core-building-block/utils"
- "encoding/json"
"time"
"github.com/google/uuid"
@@ -45,24 +44,23 @@ func (m *recoveryMfaImpl) verify(context storage.TransactionContext, mfa *model.
return nil, errors.ErrorData(logutils.StatusMissing, "mfa params", nil)
}
- var codes []string
- data, err := json.Marshal(mfa.Params["codes"])
+ codes, err := utils.JSONConvert[[]string, interface{}](mfa.Params["codes"])
if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionMarshal, "stored recovery codes", nil, err)
+ return nil, errors.WrapErrorAction(logutils.ActionParse, "stored recovery codes", nil, err)
}
- err = json.Unmarshal(data, &codes)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, "stored recovery codes", nil, err)
+ if codes == nil {
+ return nil, errors.ErrorData(logutils.StatusInvalid, "stored recovery codes", nil)
}
+ recoveryCodes := *codes
- if len(codes) == 0 {
+ if len(recoveryCodes) == 0 {
message := "no valid codes"
return &message, errors.ErrorData(logutils.StatusMissing, "recovery codes", nil)
}
- for i, rc := range codes {
+ for i, rc := range recoveryCodes {
if code == rc {
- mfa.Params["codes"] = append(codes[:i], codes[i+1:]...)
+ mfa.Params["codes"] = append(recoveryCodes[:i], recoveryCodes[i+1:]...)
now := time.Now().UTC()
mfa.DateUpdated = &now
@@ -81,11 +79,7 @@ func (m *recoveryMfaImpl) verify(context storage.TransactionContext, mfa *model.
func (m *recoveryMfaImpl) enroll(identifier string) (*model.MFAType, error) {
codes := make([]string, numCodes)
for i := 0; i < numCodes; i++ {
- newCode, err := utils.GenerateRandomString(codeLength)
- if err != nil {
- return nil, errors.WrapErrorAction("generating", "recovery code", nil, err)
- }
- codes[i] = string(newCode)
+ codes[i] = string(utils.GenerateRandomString(codeLength))
}
params := map[string]interface{}{
diff --git a/core/auth/service_static_token.go b/core/auth/service_static_token.go
index 0c998effb..6fa529918 100644
--- a/core/auth/service_static_token.go
+++ b/core/auth/service_static_token.go
@@ -18,7 +18,6 @@ import (
"core-building-block/core/model"
"core-building-block/utils"
"encoding/base64"
- "encoding/json"
"time"
"github.com/google/uuid"
@@ -47,15 +46,9 @@ type staticTokenServiceAuthImpl struct {
}
func (s *staticTokenServiceAuthImpl) checkCredentials(_ *sigauth.Request, creds interface{}, params map[string]interface{}) ([]model.ServiceAccount, error) {
- credsData, err := json.Marshal(creds)
+ tokenCreds, err := utils.JSONConvert[staticTokenCreds, interface{}](creds)
if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionMarshal, TypeStaticTokenCreds, nil, err)
- }
-
- var tokenCreds staticTokenCreds
- err = json.Unmarshal([]byte(credsData), &tokenCreds)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, TypeStaticTokenCreds, nil, err)
+ return nil, errors.WrapErrorAction(logutils.ActionParse, TypeStaticTokenCreds, nil, err)
}
validate := validator.New()
@@ -95,10 +88,7 @@ func (s *staticTokenServiceAuthImpl) addCredentials(creds *model.ServiceAccountC
return nil, errors.ErrorData(logutils.StatusMissing, model.TypeServiceAccountCredential, nil)
}
- token, err := s.auth.buildRefreshToken()
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionCreate, logutils.TypeToken, nil, err)
- }
+ token := utils.GenerateRandomString(refreshTokenLength)
creds.ID = uuid.NewString()
creds.Secrets = map[string]interface{}{
diff --git a/core/interfaces.go b/core/interfaces.go
index af4741977..078415ba6 100644
--- a/core/interfaces.go
+++ b/core/interfaces.go
@@ -167,7 +167,7 @@ type Storage interface {
FindAccountByID(context storage.TransactionContext, id string) (*model.Account, error)
FindAccounts(context storage.TransactionContext, limit *int, offset *int, appID string, orgID string, accountID *string, firstName *string, lastName *string, authType *string,
- authTypeIdentifier *string, anonymous *bool, hasPermissions *bool, permissions []string, roleIDs []string, groupIDs []string) ([]model.Account, error)
+ identifier *string, anonymous *bool, hasPermissions *bool, permissions []string, roleIDs []string, groupIDs []string) ([]model.Account, error)
FindPublicAccounts(context storage.TransactionContext, appID string, orgID string, limit *int, offset *int,
search *string, firstName *string, lastName *string, username *string, followingID *string, followerID *string, userID string) ([]model.PublicAccount, error)
FindAccountsByParams(searchParams map[string]interface{}, appID string, orgID string, limit int, offset int, allAccess bool, approvedKeys []string) ([]map[string]interface{}, error)
@@ -202,8 +202,8 @@ type Storage interface {
FindConfig(configType string, appID string, orgID string) (*model.Config, error)
FindConfigByID(id string) (*model.Config, error)
FindConfigs(configType *string) ([]model.Config, error)
- InsertConfig(config model.Config) error
- UpdateConfig(config model.Config) error
+ InsertConfig(context storage.TransactionContext, config model.Config) error
+ UpdateConfig(context storage.TransactionContext, config model.Config) error
DeleteConfig(id string) error
FindPermissionsByName(context storage.TransactionContext, names []string) ([]model.Permission, error)
diff --git a/core/mocks/Storage.go b/core/mocks/Storage.go
index 82ee9850f..dbbd9fefd 100644
--- a/core/mocks/Storage.go
+++ b/core/mocks/Storage.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.20.0. DO NOT EDIT.
+// Code generated by mockery v2.33.2. DO NOT EDIT.
package mocks
@@ -297,17 +297,17 @@ func (_m *Storage) FindAccountByID(context storage.TransactionContext, id string
return r0, r1
}
-// FindAccounts provides a mock function with given fields: context, limit, offset, appID, orgID, accountID, firstName, lastName, authType, authTypeIdentifier, anonymous, hasPermissions, permissions, roleIDs, groupIDs
-func (_m *Storage) FindAccounts(context storage.TransactionContext, limit *int, offset *int, appID string, orgID string, accountID *string, firstName *string, lastName *string, authType *string, authTypeIdentifier *string, anonymous *bool, hasPermissions *bool, permissions []string, roleIDs []string, groupIDs []string) ([]model.Account, error) {
- ret := _m.Called(context, limit, offset, appID, orgID, accountID, firstName, lastName, authType, authTypeIdentifier, anonymous, hasPermissions, permissions, roleIDs, groupIDs)
+// FindAccounts provides a mock function with given fields: context, limit, offset, appID, orgID, accountID, firstName, lastName, authType, identifier, anonymous, hasPermissions, permissions, roleIDs, groupIDs
+func (_m *Storage) FindAccounts(context storage.TransactionContext, limit *int, offset *int, appID string, orgID string, accountID *string, firstName *string, lastName *string, authType *string, identifier *string, anonymous *bool, hasPermissions *bool, permissions []string, roleIDs []string, groupIDs []string) ([]model.Account, error) {
+ ret := _m.Called(context, limit, offset, appID, orgID, accountID, firstName, lastName, authType, identifier, anonymous, hasPermissions, permissions, roleIDs, groupIDs)
var r0 []model.Account
var r1 error
if rf, ok := ret.Get(0).(func(storage.TransactionContext, *int, *int, string, string, *string, *string, *string, *string, *string, *bool, *bool, []string, []string, []string) ([]model.Account, error)); ok {
- return rf(context, limit, offset, appID, orgID, accountID, firstName, lastName, authType, authTypeIdentifier, anonymous, hasPermissions, permissions, roleIDs, groupIDs)
+ return rf(context, limit, offset, appID, orgID, accountID, firstName, lastName, authType, identifier, anonymous, hasPermissions, permissions, roleIDs, groupIDs)
}
if rf, ok := ret.Get(0).(func(storage.TransactionContext, *int, *int, string, string, *string, *string, *string, *string, *string, *bool, *bool, []string, []string, []string) []model.Account); ok {
- r0 = rf(context, limit, offset, appID, orgID, accountID, firstName, lastName, authType, authTypeIdentifier, anonymous, hasPermissions, permissions, roleIDs, groupIDs)
+ r0 = rf(context, limit, offset, appID, orgID, accountID, firstName, lastName, authType, identifier, anonymous, hasPermissions, permissions, roleIDs, groupIDs)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]model.Account)
@@ -315,7 +315,7 @@ func (_m *Storage) FindAccounts(context storage.TransactionContext, limit *int,
}
if rf, ok := ret.Get(1).(func(storage.TransactionContext, *int, *int, string, string, *string, *string, *string, *string, *string, *bool, *bool, []string, []string, []string) error); ok {
- r1 = rf(context, limit, offset, appID, orgID, accountID, firstName, lastName, authType, authTypeIdentifier, anonymous, hasPermissions, permissions, roleIDs, groupIDs)
+ r1 = rf(context, limit, offset, appID, orgID, accountID, firstName, lastName, authType, identifier, anonymous, hasPermissions, permissions, roleIDs, groupIDs)
} else {
r1 = ret.Error(1)
}
@@ -1343,13 +1343,13 @@ func (_m *Storage) InsertAuthType(context storage.TransactionContext, authType m
return r0, r1
}
-// InsertConfig provides a mock function with given fields: config
-func (_m *Storage) InsertConfig(config model.Config) error {
- ret := _m.Called(config)
+// InsertConfig provides a mock function with given fields: context, config
+func (_m *Storage) InsertConfig(context storage.TransactionContext, config model.Config) error {
+ ret := _m.Called(context, config)
var r0 error
- if rf, ok := ret.Get(0).(func(model.Config) error); ok {
- r0 = rf(config)
+ if rf, ok := ret.Get(0).(func(storage.TransactionContext, model.Config) error); ok {
+ r0 = rf(context, config)
} else {
r0 = ret.Error(0)
}
@@ -1640,13 +1640,13 @@ func (_m *Storage) UpdateAuthTypes(ID string, code string, description string, i
return r0
}
-// UpdateConfig provides a mock function with given fields: config
-func (_m *Storage) UpdateConfig(config model.Config) error {
- ret := _m.Called(config)
+// UpdateConfig provides a mock function with given fields: context, config
+func (_m *Storage) UpdateConfig(context storage.TransactionContext, config model.Config) error {
+ ret := _m.Called(context, config)
var r0 error
- if rf, ok := ret.Get(0).(func(model.Config) error); ok {
- r0 = rf(config)
+ if rf, ok := ret.Get(0).(func(storage.TransactionContext, model.Config) error); ok {
+ r0 = rf(context, config)
} else {
r0 = ret.Error(0)
}
@@ -1682,13 +1682,12 @@ func (_m *Storage) UpdatePermission(item model.Permission) error {
return r0
}
-type mockConstructorTestingTNewStorage interface {
+// NewStorage creates a new instance of Storage. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewStorage(t interface {
mock.TestingT
Cleanup(func())
-}
-
-// NewStorage creates a new instance of Storage. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-func NewStorage(t mockConstructorTestingTNewStorage) *Storage {
+}) *Storage {
mock := &Storage{}
mock.Mock.Test(t)
diff --git a/core/model/application.go b/core/model/application.go
index 1c10ff912..6b3549985 100644
--- a/core/model/application.go
+++ b/core/model/application.go
@@ -349,18 +349,19 @@ func (ao ApplicationOrganization) FindIdentityProviderSetting(identityProviderID
return nil
}
-// IsAuthTypeSupported checks if an auth type is supported for application type
-func (ao ApplicationOrganization) IsAuthTypeSupported(appType ApplicationType, authType AuthType) bool {
+// FindSupportedAuthType finds a supported auth type for application type
+func (ao ApplicationOrganization) FindSupportedAuthType(appType ApplicationType, authType AuthType) *SupportedAuthType {
for _, sat := range ao.SupportedAuthTypes {
if sat.AppTypeID == appType.ID {
for _, at := range sat.SupportedAuthTypes {
if at.AuthTypeID == authType.ID {
- return true
+ at.AuthType = authType
+ return &at
}
}
}
}
- return false
+ return nil
}
// IdentityProviderSetting represents identity provider setting for an organization in an application
@@ -376,8 +377,10 @@ func (ao ApplicationOrganization) IsAuthTypeSupported(appType ApplicationType, a
type IdentityProviderSetting struct {
IdentityProviderID string `bson:"identity_provider_id"`
- UserIdentifierField string `bson:"user_identifier_field"`
- ExternalIDFields map[string]string `bson:"external_id_fields"`
+ UserIdentifierField string `bson:"user_identifier_field"`
+ ExternalIDFields map[string]string `bson:"external_id_fields"`
+ SensitiveExternalIDs []string `bson:"sensitive_external_ids"`
+ IsEmailVerified bool `bson:"is_email_verified"`
FirstNameField string `bson:"first_name_field"`
MiddleNameField string `bson:"middle_name_field"`
@@ -457,7 +460,8 @@ type AuthTypesSupport struct {
// SupportedAuthType represents a supported auth type
type SupportedAuthType struct {
AuthTypeID string `bson:"auth_type_id"`
- Params map[string]interface{} `bson:"params"`
+ Params map[string]interface{} `bson:"params,omitempty"`
+ AuthType AuthType `bson:"-"`
}
// ApplicationConfig represents app configs
diff --git a/core/model/auth.go b/core/model/auth.go
index 03f59d03c..ba1505561 100644
--- a/core/model/auth.go
+++ b/core/model/auth.go
@@ -27,6 +27,8 @@ import (
const (
//TypeLoginSession login session type
TypeLoginSession logutils.MessageDataType = "login session"
+ //TypeLoginState login state type
+ TypeLoginState logutils.MessageDataType = "login state"
//TypeAuthType auth type
TypeAuthType logutils.MessageDataType = "auth type"
//TypeIdentityProvider identity provider type
@@ -90,9 +92,8 @@ type LoginSession struct {
Anonymous bool
- Identifier string //it is the account id(anonymous id for anonymous logins)
- ExternalIDs map[string]string
- AccountAuthType *AccountAuthType //it may be nil for anonymous logins
+ Identifier string //it is the account id(anonymous id for anonymous logins)
+ Account *Account //it may be nil for anonymous logins
Device *Device
@@ -220,6 +221,18 @@ func (ls LoginSession) LogInfo() string {
ls.StateExpires, ls.MfaAttempts, ls.DateRefreshed, ls.DateUpdated, ls.DateCreated)
}
+// LoginState represents a state variable generated during a login request and used to complete that request (by generating a LoginSession)
+type LoginState struct {
+ ID string `bson:"_id"`
+ AppID string `bson:"app_id"`
+ OrgID string `bson:"org_id"`
+
+ AccountID *string `bson:"account_id"`
+
+ State map[string]interface{} `bson:"state"`
+ DateCreated time.Time `bson:"date_created"`
+}
+
// APIKey represents an API key entity
type APIKey struct {
ID string `json:"id" bson:"_id"`
@@ -239,6 +252,7 @@ type AuthType struct {
UseCredentials bool `bson:"use_credentials"` //says if the auth type uses credentials
IgnoreMFA bool `bson:"ignore_mfa"` //says if login using this auth type may bypass account MFA
Params map[string]interface{} `bson:"params"`
+ Aliases []string `bson:"aliases,omitempty"`
}
// IdentityProvider represents identity provider entity
diff --git a/core/model/config.go b/core/model/config.go
index b6041236b..770ff630d 100644
--- a/core/model/config.go
+++ b/core/model/config.go
@@ -29,11 +29,15 @@ const (
TypeConfigData logutils.MessageDataType = "config data"
// TypeEnvConfigData env configs type
TypeEnvConfigData logutils.MessageDataType = "env config data"
+ // TypeAuthConfigData auth configs type
+ TypeAuthConfigData logutils.MessageDataType = "auth config data"
//TypeOrganizationConfig ...
TypeOrganizationConfig logutils.MessageDataType = "org config"
// ConfigTypeEnv is the Config type for EnvConfigData
ConfigTypeEnv string = "env"
+ // ConfigTypeAuth is the Config type for AuthConfigData
+ ConfigTypeAuth string = "auth"
)
// Config contains generic configs
@@ -54,6 +58,13 @@ type EnvConfigData struct {
CORSAllowedHeaders []string `json:"cors_allowed_headers" bson:"cors_allowed_headers"`
}
+// AuthConfigData contains auth configs for this service
+type AuthConfigData struct {
+ EmailShouldVerify *bool `json:"email_should_verify" bson:"email_should_verify"`
+ EmailVerifyWaitTime *int `json:"email_verify_wait_time" bson:"email_verify_wait_time"`
+ EmailVerifyExpiry *int `json:"email_verify_expiry" bson:"email_verify_expiry"`
+}
+
// GetConfigData returns a pointer to the given config's Data as the given type T
func GetConfigData[T ConfigData](c Config) (*T, error) {
if data, ok := c.Data.(T); ok {
@@ -64,7 +75,7 @@ func GetConfigData[T ConfigData](c Config) (*T, error) {
// ConfigData represents any set of data that may be stored in a config
type ConfigData interface {
- EnvConfigData | map[string]interface{}
+ EnvConfigData | AuthConfigData | map[string]interface{}
}
// OrganizationConfig represents configuration for an organization
diff --git a/core/model/user.go b/core/model/user.go
index e96973c1d..a38598441 100644
--- a/core/model/user.go
+++ b/core/model/user.go
@@ -33,6 +33,8 @@ const (
TypeAccountSystemConfigs logutils.MessageDataType = "account system configs"
//TypeAccountAuthType account auth type
TypeAccountAuthType logutils.MessageDataType = "account auth type"
+ //TypeAccountIdentifier account identifier
+ TypeAccountIdentifier logutils.MessageDataType = "account identifier"
//TypeAccountPermissions account permissions
TypeAccountPermissions logutils.MessageDataType = "account permissions"
//TypeAccountRoles account roles
@@ -76,12 +78,11 @@ type Account struct {
Groups []AccountGroup
Scopes []string
- AuthTypes []AccountAuthType
+ Identifiers []AccountIdentifier
+ AuthTypes []AccountAuthType
MFATypes []MFAType
- Username string
- ExternalIDs map[string]string
Preferences map[string]interface{}
SystemConfigs map[string]interface{}
Profile Profile //one account has one profile, one profile can be shared between many accounts
@@ -111,21 +112,81 @@ func (a Account) GetAccountAuthTypeByID(ID string) *AccountAuthType {
return nil
}
-// GetAccountAuthType finds account auth type
-func (a Account) GetAccountAuthType(authTypeID string, identifier string) *AccountAuthType {
+// GetAccountAuthTypes finds account auth types
+func (a Account) GetAccountAuthTypes(authTypeIDorCode string) []AccountAuthType {
+ authTypes := make([]AccountAuthType, 0)
for _, aat := range a.AuthTypes {
- if aat.AuthType.ID == authTypeID && aat.Identifier == identifier {
+ if aat.SupportedAuthType.AuthType.ID == authTypeIDorCode || aat.SupportedAuthType.AuthType.Code == authTypeIDorCode {
aat.Account = a
- return &aat
+ authTypes = append(authTypes, aat)
}
}
- return nil
+ return authTypes
}
-// SortAccountAuthTypes sorts account auth types by matching the given uid
-func (a Account) SortAccountAuthTypes(uid string) {
+// SortAccountAuthTypes sorts account auth types by matching the given id
+func (a Account) SortAccountAuthTypes(id string, authType string) {
sort.Slice(a.AuthTypes, func(i, _ int) bool {
- return a.AuthTypes[i].Identifier == uid
+ return (id != "" && a.AuthTypes[i].ID == id) || (authType != "" && a.AuthTypes[i].SupportedAuthType.AuthType.Code == authType)
+ })
+}
+
+// GetAccountIdentifier finds account identifier
+func (a Account) GetAccountIdentifier(code string, identifier string) *AccountIdentifier {
+ for _, id := range a.Identifiers {
+ if code != "" && id.Code == code && identifier != "" && id.Identifier == identifier {
+ id.Account = a
+ return &id
+ }
+ if code != "" && id.Code == code {
+ id.Account = a
+ return &id
+ }
+ if identifier != "" && id.Identifier == identifier {
+ id.Account = a
+ return &id
+ }
+ }
+ return nil
+}
+
+// GetAccountIdentifierByID finds account identifier by its ID
+func (a Account) GetAccountIdentifierByID(id string) *AccountIdentifier {
+ for _, ai := range a.Identifiers {
+ if ai.ID == id {
+ ai.Account = a
+ return &ai
+ }
+ }
+ return nil
+}
+
+// GetVerifiedAccountIdentifiers returns a list of only verified identifiers for this account
+func (a Account) GetVerifiedAccountIdentifiers() []AccountIdentifier {
+ identifiers := make([]AccountIdentifier, 0)
+ for _, id := range a.Identifiers {
+ if id.Verified {
+ identifiers = append(identifiers, id)
+ }
+ }
+ return identifiers
+}
+
+// GetExternalAccountIdentifiers returns a list of only external identifiers for this account
+func (a Account) GetExternalAccountIdentifiers() []AccountIdentifier {
+ identifiers := make([]AccountIdentifier, 0)
+ for _, id := range a.Identifiers {
+ if id.AccountAuthTypeID != nil {
+ identifiers = append(identifiers, id)
+ }
+ }
+ return identifiers
+}
+
+// SortAccountIdentifiers sorts account identifiers by matching the given identifier
+func (a Account) SortAccountIdentifiers(identifier string) {
+ sort.Slice(a.Identifiers, func(i, _ int) bool {
+ return a.Identifiers[i].Identifier == identifier
})
}
@@ -334,56 +395,30 @@ func AccountGroupsFromAppOrgGroups(items []AppOrgGroup, active bool, adminSet bo
type AccountAuthType struct {
ID string
- AuthType AuthType //one of the supported auth type
- Account Account
+ SupportedAuthType SupportedAuthType //one of the supported auth type
+ Account Account
- Identifier string
- Params map[string]interface{}
+ Params map[string]interface{}
Credential *Credential //this can be nil as the external auth types authenticates the users outside the system
- Active bool
- Unverified bool
- Linked bool
+ Active bool
DateCreated time.Time
DateUpdated *time.Time
}
-// SetUnverified sets the Unverified flag to value in the account auth type itself and the appropriate account auth type within the account member
-func (aat *AccountAuthType) SetUnverified(value bool) {
- if aat == nil {
- return
- }
-
- aat.Unverified = false
- for i := 0; i < len(aat.Account.AuthTypes); i++ {
- if aat.Account.AuthTypes[i].ID == aat.ID {
- aat.Account.AuthTypes[i].Unverified = false
- }
- }
-}
-
// Equals checks if two account auth types are equal
func (aat *AccountAuthType) Equals(other AccountAuthType) bool {
- if aat.Identifier != other.Identifier {
- return false
- }
if aat.Account.ID != other.Account.ID {
return false
}
- if aat.AuthType.Code != other.AuthType.Code {
+ if aat.SupportedAuthType.AuthType.Code != other.SupportedAuthType.AuthType.Code {
return false
}
if aat.Active != other.Active {
return false
}
- if aat.Unverified != other.Unverified {
- return false
- }
- if aat.Linked != other.Linked {
- return false
- }
if !utils.DeepEqual(aat.Params, other.Params) {
return false
}
@@ -399,13 +434,66 @@ func (aat *AccountAuthType) Equals(other AccountAuthType) bool {
return true
}
+// AccountIdentifier represents account identifiers
+type AccountIdentifier struct {
+ ID string
+ Code string
+ Identifier string
+
+ Verified bool
+ Linked bool
+ Sensitive bool
+
+ AccountAuthTypeID *string
+ Primary *bool
+
+ Account Account
+
+ VerificationCode *string
+ VerificationExpiry *time.Time
+
+ DateCreated time.Time
+ DateUpdated *time.Time
+}
+
+// SetVerified sets the Verified flag to value in the account auth type itself and the appropriate account auth type within the account member
+func (ai *AccountIdentifier) SetVerified(value bool) {
+ if ai == nil {
+ return
+ }
+
+ ai.Verified = value
+ for i := 0; i < len(ai.Account.Identifiers); i++ {
+ if ai.Account.Identifiers[i].Identifier == ai.Identifier {
+ ai.Account.Identifiers[i].Verified = value
+ }
+ }
+}
+
+// Equals checks if two account identifiers are equal
+func (ai *AccountIdentifier) Equals(other AccountIdentifier) bool {
+ if ai.Identifier != other.Identifier {
+ return false
+ }
+ if ai.Account.ID != other.Account.ID {
+ return false
+ }
+ if ai.Verified != other.Verified {
+ return false
+ }
+ if ai.Linked != other.Linked {
+ return false
+ }
+
+ return true
+}
+
// Credential represents a credential for account auth type/s
type Credential struct {
ID string
AuthType AuthType
- AccountsAuthTypes []AccountAuthType //one credential can be used for more than one account auth type
- Verified bool
+ AccountsAuthTypes []AccountAuthType //one credential can be used for more than one account auth type
Value map[string]interface{} //credential value
DateCreated time.Time
@@ -435,8 +523,6 @@ type Profile struct {
PhotoURL string
FirstName string
LastName string
- Email string
- Phone string
BirthYear int16
Address string
ZipCode string
@@ -469,12 +555,6 @@ func (p Profile) Merge(src Profile) Profile {
if src.LastName != "" {
p.LastName = src.LastName
}
- if src.Email != "" {
- p.Email = src.Email
- }
- if src.Phone != "" {
- p.Phone = src.Phone
- }
if src.Address != "" {
p.Address = src.Address
}
@@ -518,14 +598,6 @@ func ProfileFromMap(profileMap map[string]interface{}) Profile {
if typeVal, ok := val.(string); ok {
profile.LastName = typeVal
}
- } else if key == "email" {
- if typeVal, ok := val.(string); ok {
- profile.Email = typeVal
- }
- } else if key == "phone" {
- if typeVal, ok := val.(string); ok {
- profile.Phone = typeVal
- }
} else if key == "birth_year" {
if typeVal, ok := val.(int16); ok {
profile.BirthYear = typeVal
@@ -548,7 +620,7 @@ func ProfileFromMap(profileMap map[string]interface{}) Profile {
}
} else if key == "photo_url" {
if typeVal, ok := val.(string); ok {
- profile.Phone = typeVal
+ profile.PhotoURL = typeVal
}
} else {
profile.UnstructuredProperties[key] = val
@@ -573,8 +645,10 @@ type Device struct {
// ExternalSystemUser represents external system user
type ExternalSystemUser struct {
- Identifier string `json:"identifier" bson:"identifier"` //this is the identifier used in our system to map the user
- ExternalIDs map[string]string `json:"external_ids" bson:"external_ids"`
+ Identifier string `json:"identifier" bson:"identifier"` //this is the identifier used in our system to map the user
+ ExternalIDs map[string]string `json:"external_ids" bson:"external_ids"`
+ SensitiveExternalIDs []string `json:"sensitive_external_ids" bson:"sensitive_external_ids"`
+ IsEmailVerified bool `json:"is_email_verified" bson:"is_email_verified"`
//these are common fields which should be popuated by the external system
FirstName string `json:"first_name" bson:"first_name"`
diff --git a/driven/phoneverifier/twilio_adapter.go b/driven/phoneverifier/twilio_adapter.go
new file mode 100644
index 000000000..532f8f0d5
--- /dev/null
+++ b/driven/phoneverifier/twilio_adapter.go
@@ -0,0 +1,191 @@
+// Copyright 2023 Board of Trustees of the University of Illinois.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package phoneverifier
+
+import (
+ "context"
+ "core-building-block/utils"
+ "encoding/base64"
+ "encoding/json"
+ "io"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+
+ "github.com/rokwire/logging-library-go/v2/errors"
+ "github.com/rokwire/logging-library-go/v2/logutils"
+)
+
+const (
+ // TypeTwilio refers to the twilio specific phone identifier type
+ TypeTwilio logutils.MessageDataType = "twilio_phone"
+
+ servicesPathPart = "https://verify.twilio.com/v2/Services"
+ verificationsPathPart = "Verifications"
+ verificationCheckPart = "VerificationCheck"
+ typeVerifyServiceID logutils.MessageDataType = "phone verification service id"
+ typeVerifyServiceToken logutils.MessageDataType = "phone verification service token"
+ typeVerificationResponse logutils.MessageDataType = "phone verification response"
+ typeVerificationStatus logutils.MessageDataType = "phone verification staus"
+ typeVerificationSID logutils.MessageDataType = "phone verification sid"
+)
+
+// TwilioAdapter implements the Emailer interface
+type TwilioAdapter struct {
+ accountSID string
+ token string
+ serviceSID string
+ httpClient *http.Client
+}
+
+type verifyPhoneResponse struct {
+ Status string `json:"status"`
+ Payee interface{} `json:"payee"`
+ DateUpdated time.Time `json:"date_updated"`
+ AccountSid string `json:"account_sid"`
+ To string `json:"to"`
+ Amount interface{} `json:"amount"`
+ Valid bool `json:"valid"`
+ URL string `json:"url"`
+ Sid string `json:"sid"`
+ DateCreated time.Time `json:"date_created"`
+ ServiceSid string `json:"service_sid"`
+ Channel string `json:"channel"`
+}
+
+type checkStatusResponse struct {
+ Sid string `json:"sid"`
+ ServiceSid string `json:"service_sid"`
+ AccountSid string `json:"account_sid"`
+ To string `json:"to" validate:"required"`
+ Channel string `json:"channel"`
+ Status string `json:"status"`
+ Amount interface{} `json:"amount"`
+ Payee interface{} `json:"payee"`
+ DateCreated time.Time `json:"date_created"`
+ DateUpdated time.Time `json:"date_updated"`
+}
+
+// StartVerification begins the phone verification process
+func (a *TwilioAdapter) StartVerification(phone string, data url.Values) error {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ body, err := a.makeRequest(ctx, "POST", servicesPathPart+"/"+a.serviceSID+"/"+verificationsPathPart, data, a.accountSID, a.token)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionSend, logutils.TypeRequest, &logutils.FieldArgs{"verification params": data}, err)
+ }
+
+ var verifyResult verifyPhoneResponse
+ err = json.Unmarshal(body, &verifyResult)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionUnmarshal, typeVerificationResponse, nil, err)
+ }
+
+ if verifyResult.To != phone {
+ return errors.ErrorData(logutils.StatusInvalid, logutils.TypeString, &logutils.FieldArgs{"expected phone": phone, "actual phone": verifyResult.To})
+ }
+ if verifyResult.Status != "pending" {
+ return errors.ErrorData(logutils.StatusInvalid, typeVerificationStatus, &logutils.FieldArgs{"expected pending, actual:": verifyResult.Status})
+ }
+ if verifyResult.Sid == "" {
+ return errors.ErrorData(logutils.StatusMissing, typeVerificationSID, nil)
+ }
+
+ return nil
+}
+
+// CheckVerification verifies the code sent to a user's phone to finish verification
+func (a *TwilioAdapter) CheckVerification(phone string, data url.Values) error {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ body, err := a.makeRequest(ctx, "POST", servicesPathPart+"/"+a.serviceSID+"/"+verificationCheckPart, data, a.accountSID, a.token)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionSend, logutils.TypeRequest, nil, err)
+ }
+
+ var checkResponse checkStatusResponse
+ err = json.Unmarshal(body, &checkResponse)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionUnmarshal, typeVerificationResponse, nil, err)
+ }
+
+ if checkResponse.To != phone {
+ return errors.ErrorData(logutils.StatusInvalid, logutils.TypeString, &logutils.FieldArgs{"expected phone": phone, "actual phone": checkResponse.To})
+ }
+ if checkResponse.Status != "approved" {
+ return errors.ErrorData(logutils.StatusInvalid, typeVerificationStatus, &logutils.FieldArgs{"expected approved, actual:": checkResponse.Status}).SetStatus(utils.ErrorStatusInvalid)
+ }
+
+ return nil
+}
+
+func (a *TwilioAdapter) makeRequest(ctx context.Context, method string, pathPart string, data url.Values, user string, token string) ([]byte, error) {
+ rb := new(strings.Reader)
+ logAction := logutils.ActionSend
+
+ if data != nil && (method == "POST" || method == "PUT") {
+ rb = strings.NewReader(data.Encode())
+ }
+ if method == "GET" && data != nil {
+ pathPart = pathPart + "?" + data.Encode()
+ logAction = logutils.ActionRead
+ }
+
+ req, err := http.NewRequest(method, pathPart, rb)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logAction, logutils.TypeRequest, &logutils.FieldArgs{"path": pathPart}, err)
+ }
+
+ if token != "" {
+ req.Header.Add("Authorization", "Basic "+a.basicAuth(user, token))
+ }
+ req.Header.Add("Accept", "application/json")
+ req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+
+ resp, err := a.httpClient.Do(req)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logAction, logutils.TypeRequest, nil, err)
+ }
+
+ defer resp.Body.Close()
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionRead, logutils.TypeRequestBody, nil, err)
+ }
+ if resp.StatusCode != 200 && resp.StatusCode != 201 {
+ return nil, errors.ErrorData(logutils.StatusInvalid, logutils.TypeResponse, &logutils.FieldArgs{"status_code": resp.StatusCode, "error": string(body)})
+ }
+ return body, nil
+}
+
+func (a *TwilioAdapter) basicAuth(username, password string) string {
+ auth := username + ":" + password
+ return base64.StdEncoding.EncodeToString([]byte(auth))
+}
+
+// NewTwilioAdapter creates a new twilio phone verifier adapter instance
+func NewTwilioAdapter(accountSID string, token string, serviceSID string) (*TwilioAdapter, error) {
+ if accountSID == "" {
+ return nil, errors.ErrorData(logutils.StatusMissing, typeVerifyServiceID, nil)
+ }
+ if token == "" {
+ return nil, errors.ErrorData(logutils.StatusMissing, typeVerifyServiceToken, nil)
+ }
+
+ client := &http.Client{}
+ return &TwilioAdapter{accountSID: accountSID, token: token, serviceSID: serviceSID, httpClient: client}, nil
+}
diff --git a/driven/profilebb/adapter.go b/driven/profilebb/adapter.go
index e06399d3e..58c684039 100644
--- a/driven/profilebb/adapter.go
+++ b/driven/profilebb/adapter.go
@@ -173,10 +173,9 @@ func (a *Adapter) GetProfileBBData(queryParams map[string]string, l *logs.Log) (
l.WarnError(logutils.MessageActionError(logutils.ActionParse, "date created", nil), err)
dateCreated = &now
}
- existingProfile := model.Profile{FirstName: profileData.PII.FirstName, LastName: profileData.PII.LastName,
- Email: profileData.PII.Email, Phone: profileData.PII.Phone, BirthYear: profileData.PII.BirthYear,
- Address: profileData.PII.Address, ZipCode: profileData.PII.ZipCode, State: profileData.PII.State,
- Country: profileData.PII.Country, DateCreated: *dateCreated, DateUpdated: &now}
+ existingProfile := model.Profile{FirstName: profileData.PII.FirstName, LastName: profileData.PII.LastName, BirthYear: profileData.PII.BirthYear,
+ Address: profileData.PII.Address, ZipCode: profileData.PII.ZipCode, State: profileData.PII.State, Country: profileData.PII.Country,
+ DateCreated: *dateCreated, DateUpdated: &now}
preferences := a.reformatPreferences(profileData.NonPII, l)
diff --git a/driven/storage/adapter.go b/driven/storage/adapter.go
index 2088075ad..6e4b6037b 100644
--- a/driven/storage/adapter.go
+++ b/driven/storage/adapter.go
@@ -121,6 +121,11 @@ func (sa *Adapter) Start() error {
return errors.WrapErrorAction(logutils.ActionCache, model.TypeConfig, nil, err)
}
+ err = sa.migrateAuthTypes()
+ if err != nil {
+ return errors.WrapErrorAction("migrating", model.TypeAuthType, nil, err)
+ }
+
return err
}
@@ -454,6 +459,9 @@ func (sa *Adapter) setCachedAuthTypes(authProviders []model.AuthType) {
func (sa *Adapter) setCachedAuthType(authType model.AuthType) {
sa.cachedAuthTypes.Store(authType.ID, authType)
sa.cachedAuthTypes.Store(authType.Code, authType)
+ for _, alias := range authType.Aliases {
+ sa.cachedAuthTypes.Store(alias, authType)
+ }
}
func (sa *Adapter) getCachedAuthType(key string) (*model.AuthType, error) {
@@ -736,6 +744,8 @@ func (sa *Adapter) setCachedConfigs(configs []model.Config) {
switch config.Type {
case model.ConfigTypeEnv:
err = parseConfigsData[model.EnvConfigData](&config)
+ case model.ConfigTypeAuth:
+ err = parseConfigsData[model.AuthConfigData](&config)
default:
err = parseConfigsData[map[string]interface{}](&config)
}
@@ -1034,7 +1044,7 @@ func (sa *Adapter) buildLoginSession(context TransactionContext, ls *loginSessio
//account - from storage
var account *model.Account
var err error
- if ls.AccountAuthTypeID != nil {
+ if !ls.Anonymous {
account, err = sa.FindAccountByID(context, ls.Identifier)
if err != nil {
return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, &logutils.FieldArgs{"_id": ls.Identifier}, err)
@@ -1121,11 +1131,6 @@ func (sa *Adapter) DeleteLoginSessionByID(context TransactionContext, id string)
return sa.deleteLoginSessions(context, "_id", id, true)
}
-// DeleteLoginSessionsByAccountAuthTypeID deletes login sessions by account auth type ID
-func (sa *Adapter) DeleteLoginSessionsByAccountAuthTypeID(context TransactionContext, id string) error {
- return sa.deleteLoginSessions(context, "account_auth_type_id", id, false)
-}
-
func (sa *Adapter) deleteLoginSessions(context TransactionContext, key string, value string, checkDeletedCount bool) error {
filter := bson.M{key: value}
@@ -1208,11 +1213,49 @@ func (sa *Adapter) FindSessionsLazy(appID string, orgID string) ([]model.LoginSe
return sessions, nil
}
-// FindAccount finds an account for app, org, auth type and account auth type identifier
-func (sa *Adapter) FindAccount(context TransactionContext, appOrgID string, authTypeID string, accountAuthTypeIdentifier string) (*model.Account, error) {
- filter := bson.D{primitive.E{Key: "app_org_id", Value: appOrgID},
- primitive.E{Key: "auth_types.auth_type_id", Value: authTypeID},
- primitive.E{Key: "auth_types.identifier", Value: accountAuthTypeIdentifier}}
+// FindLoginState finds a saved login state
+func (sa *Adapter) FindLoginState(appID string, orgID string, accountID *string, stateParams map[string]interface{}) (*model.LoginState, error) {
+ filter := bson.M{"app_id": appID, "org_id": orgID}
+
+ if accountID != nil {
+ filter["account_id"] = *accountID
+ }
+ for k, v := range stateParams {
+ filter["state."+k] = v
+ }
+
+ var states []model.LoginState
+ err := sa.db.loginStates.Find(filter, &states, nil)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeLoginState, nil, err)
+ }
+ if len(states) == 0 {
+ //not found
+ return nil, nil
+ }
+
+ loginState := states[0]
+ return &loginState, nil
+}
+
+// InsertLoginState inserts a new login state
+func (sa *Adapter) InsertLoginState(loginState model.LoginState) error {
+ _, err := sa.db.loginStates.InsertOne(loginState)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionInsert, model.TypeLoginState, nil, err)
+ }
+
+ 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{
+ "$elemMatch": bson.M{
+ "code": code,
+ "identifier": identifier,
+ },
+ }}
var accounts []account
err := sa.db.accounts.FindWithContext(context, filter, &accounts, nil)
if err != nil {
@@ -1233,13 +1276,13 @@ func (sa *Adapter) FindAccount(context TransactionContext, appOrgID string, auth
return nil, errors.ErrorData(logutils.StatusMissing, model.TypeApplicationOrganization, nil)
}
- modelAccount := accountFromStorage(account, *appOrg)
+ modelAccount := accountFromStorage(account, *appOrg, sa)
return &modelAccount, nil
}
// FindAccounts finds accounts
func (sa *Adapter) FindAccounts(context TransactionContext, limit *int, offset *int, appID string, orgID string, accountID *string, firstName *string, lastName *string, authType *string,
- authTypeIdentifier *string, anonymous *bool, hasPermissions *bool, permissions []string, roleIDs []string, groupIDs []string) ([]model.Account, error) {
+ identifier *string, anonymous *bool, hasPermissions *bool, permissions []string, roleIDs []string, groupIDs []string) ([]model.Account, error) {
//find app org id
appOrg, err := sa.getCachedApplicationOrganization(appID, orgID)
if err != nil {
@@ -1273,8 +1316,8 @@ func (sa *Adapter) FindAccounts(context TransactionContext, limit *int, offset *
}
filter = append(filter, primitive.E{Key: "auth_types.auth_type_id", Value: cachedAuthType.ID})
}
- if authTypeIdentifier != nil {
- filter = append(filter, primitive.E{Key: "auth_types.identifier", Value: *authTypeIdentifier})
+ if identifier != nil {
+ filter = append(filter, primitive.E{Key: "identifiers.identifier", Value: *identifier})
}
if anonymous != nil {
filter = append(filter, primitive.E{Key: "anonymous", Value: *anonymous})
@@ -1326,7 +1369,7 @@ func (sa *Adapter) FindAccounts(context TransactionContext, limit *int, offset *
return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, nil, err)
}
- accounts := accountsFromStorage(list, *appOrg)
+ accounts := accountsFromStorage(list, *appOrg, sa)
return accounts, nil
}
@@ -1367,7 +1410,7 @@ func (sa *Adapter) FindPublicAccounts(context TransactionContext, appID string,
}
regexFilter := bson.M{
"$or": []bson.M{
- {"username": primitive.Regex{Pattern: searchStr, Options: "i"}},
+ {"identifiers": bson.M{"$elemMatch": bson.M{"code": "username", "identifier": primitive.Regex{Pattern: searchStr, Options: "i"}}}},
{"profile.first_name": primitive.Regex{Pattern: searchStr, Options: "i"}},
{"profile.last_name": primitive.Regex{Pattern: searchStr, Options: "i"}},
},
@@ -1399,7 +1442,7 @@ func (sa *Adapter) FindPublicAccounts(context TransactionContext, appID string,
}
if username != nil {
usernameStr = *username
- pipeline = append(pipeline, bson.M{"$match": bson.M{"username": *username}})
+ pipeline = append(pipeline, bson.M{"$match": bson.M{"identifiers": bson.M{"$elemMatch": bson.M{"code": "username", "identifier": *username}}}})
}
if followingID != nil {
@@ -1431,9 +1474,17 @@ func (sa *Adapter) FindPublicAccounts(context TransactionContext, appID string,
var publicAccounts []model.PublicAccount
for _, account := range accounts {
+ username := ""
+ for _, id := range account.Identifiers {
+ if id.Code == "username" {
+ username = id.Identifier
+ break
+ }
+ }
+
publicAccounts = append(publicAccounts, model.PublicAccount{
ID: account.ID,
- Username: account.Username,
+ Username: username,
FirstName: account.Profile.FirstName,
LastName: account.Profile.LastName,
Verified: account.Verified,
@@ -1527,17 +1578,17 @@ func (sa *Adapter) FindAccountsByAccountID(context TransactionContext, appID str
if err != nil {
return nil, err
}
- accounts := accountsFromStorage(accountResult, *appOrg)
+ accounts := accountsFromStorage(accountResult, *appOrg, sa)
return accounts, nil
}
-// FindAccountsByUsername finds accounts with a username for a given appOrg
+// FindAccountsByUsername finds accounts by username for a given appOrg
func (sa *Adapter) FindAccountsByUsername(context TransactionContext, appOrg *model.ApplicationOrganization, username string) ([]model.Account, error) {
if appOrg == nil {
return nil, errors.ErrorData(logutils.StatusMissing, model.TypeApplicationOrganization, nil)
}
- filter := bson.D{primitive.E{Key: "app_org_id", Value: appOrg.ID}, primitive.E{Key: "username", Value: username}}
+ filter := bson.D{primitive.E{Key: "app_org_id", Value: appOrg.ID}, primitive.E{Key: "identifiers", Value: bson.M{"$elemMatch": bson.M{"code": "username", "identifier": username}}}}
var accountResult []account
err := sa.db.accounts.Find(filter, &accountResult, nil)
@@ -1548,7 +1599,7 @@ func (sa *Adapter) FindAccountsByUsername(context TransactionContext, appOrg *mo
sa.logger.WarnWithFields("duplicate username", logutils.Fields{"number": len(accountResult), "app_org_id": appOrg.ID, "username": username})
}
- accounts := accountsFromStorage(accountResult, *appOrg)
+ accounts := accountsFromStorage(accountResult, *appOrg, sa)
return accounts, nil
}
@@ -1562,6 +1613,16 @@ func (sa *Adapter) FindAccountByAuthTypeID(context TransactionContext, id string
return sa.findAccount(context, "auth_types.id", id)
}
+// FindAccountByCredentialID finds an account by auth type id
+func (sa *Adapter) FindAccountByCredentialID(context TransactionContext, id string) (*model.Account, error) {
+ return sa.findAccount(context, "auth_types.credential_id", id)
+}
+
+// FindAccountByIdentifierID finds an account by identifier id
+func (sa *Adapter) FindAccountByIdentifierID(context TransactionContext, id string) (*model.Account, error) {
+ return sa.findAccount(context, "identifiers.id", id)
+}
+
func (sa *Adapter) findAccount(context TransactionContext, key string, id string) (*model.Account, error) {
account, err := sa.findStorageAccount(context, key, id)
if err != nil {
@@ -1581,7 +1642,7 @@ func (sa *Adapter) findAccount(context TransactionContext, key string, id string
return nil, errors.ErrorData(logutils.StatusMissing, model.TypeApplicationOrganization, nil)
}
- modelAccount := accountFromStorage(*account, *appOrg)
+ modelAccount := accountFromStorage(*account, *appOrg, sa)
return &modelAccount, nil
}
@@ -1976,12 +2037,15 @@ func (sa *Adapter) UpdateAccountUsername(context TransactionContext, accountID s
filter := bson.D{primitive.E{Key: "_id", Value: accountID}}
update := bson.D{
primitive.E{Key: "$set", Value: bson.D{
- primitive.E{Key: "username", Value: username},
+ primitive.E{Key: "identifiers.$[id].identifier", Value: username},
primitive.E{Key: "date_updated", Value: time.Now().UTC()},
}},
}
- res, err := sa.db.accounts.UpdateOneWithContext(context, filter, update, nil)
+ opts := options.UpdateOptions{}
+ arrayFilters := []interface{}{bson.M{"id.code": "username"}}
+ opts.SetArrayFilters(options.ArrayFilters{Filters: arrayFilters})
+ res, err := sa.db.accounts.UpdateOneWithContext(context, filter, update, &opts)
if err != nil {
return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeAccount, &logutils.FieldArgs{"id": accountID}, err)
}
@@ -1992,7 +2056,7 @@ func (sa *Adapter) UpdateAccountUsername(context TransactionContext, accountID s
return nil
}
-// UpdateAccountVerified updates an account's username
+// UpdateAccountVerified updates an account's verified status
func (sa *Adapter) UpdateAccountVerified(context TransactionContext, accountID string, appID string, orgID string, verified bool) error {
appOrg, err := sa.FindApplicationOrganization(appID, orgID)
if err != nil || appOrg == nil {
@@ -2219,7 +2283,7 @@ func (sa *Adapter) UpdateAccountScopes(context TransactionContext, accountID str
}
// InsertAccountAuthType inserts am account auth type
-func (sa *Adapter) InsertAccountAuthType(item model.AccountAuthType) error {
+func (sa *Adapter) InsertAccountAuthType(context TransactionContext, item model.AccountAuthType) error {
storageItem := accountAuthTypeToStorage(item)
//3. first find the account record
@@ -2228,80 +2292,42 @@ func (sa *Adapter) InsertAccountAuthType(item model.AccountAuthType) error {
primitive.E{Key: "$push", Value: bson.D{
primitive.E{Key: "auth_types", Value: storageItem},
}},
+ primitive.E{Key: "$set", Value: bson.D{
+ primitive.E{Key: "date_updated", Value: time.Now().UTC()},
+ }},
}
- res, err := sa.db.accounts.UpdateOne(filter, update, nil)
+ res, err := sa.db.accounts.UpdateOneWithContext(context, filter, update, nil)
if err != nil {
return errors.WrapErrorAction(logutils.ActionInsert, model.TypeAccountAuthType, nil, err)
}
if res.ModifiedCount != 1 {
- return errors.ErrorAction(logutils.ActionUpdate, model.TypeAccountAuthType, &logutils.FieldArgs{"unexpected modified count": res.ModifiedCount})
+ return errors.ErrorAction(logutils.ActionUpdate, model.TypeAccount, &logutils.FieldArgs{"modified": res.ModifiedCount, "expected": 1})
}
return nil
}
-// UpdateAccountAuthType updates account auth type
-func (sa *Adapter) UpdateAccountAuthType(item model.AccountAuthType) error {
- // transaction
- err := sa.db.dbClient.UseSession(context.Background(), func(sessionContext mongo.SessionContext) error {
- err := sessionContext.StartTransaction()
- if err != nil {
- sa.abortTransaction(sessionContext)
- return errors.WrapErrorAction(logutils.ActionStart, logutils.TypeTransaction, nil, err)
- }
-
- //1. set time updated to the item
- now := time.Now()
- item.DateUpdated = &now
-
- //2 convert to storage item
- storageItem := accountAuthTypeToStorage(item)
-
- //3. first find the account record
- findFilter := bson.M{"auth_types.id": item.ID}
- var accounts []account
- err = sa.db.accounts.FindWithContext(sessionContext, findFilter, &accounts, nil)
- if err != nil {
- sa.abortTransaction(sessionContext)
- return errors.WrapErrorAction(logutils.ActionFind, model.TypeUserAuth, &logutils.FieldArgs{"account auth type id": item.ID}, err)
- }
- if len(accounts) == 0 {
- sa.abortTransaction(sessionContext)
- return errors.ErrorAction(logutils.ActionFind, "for some reasons account is nil for account auth type", &logutils.FieldArgs{"acccount auth type id": item.ID})
- }
- account := accounts[0]
-
- //4. update the account auth type in the account record
- accountAuthTypes := account.AuthTypes
- newAccountAuthTypes := make([]accountAuthType, len(accountAuthTypes))
- for j, aAuthType := range accountAuthTypes {
- if aAuthType.ID == storageItem.ID {
- newAccountAuthTypes[j] = storageItem
- } else {
- newAccountAuthTypes[j] = aAuthType
- }
- }
- account.AuthTypes = newAccountAuthTypes
+// UpdateAccountAuthType updates an account with the provided account auth type
+func (sa *Adapter) UpdateAccountAuthType(context TransactionContext, item model.AccountAuthType) error {
+ storageItem := accountAuthTypeToStorage(item)
+ now := time.Now().UTC()
+ storageItem.DateUpdated = &now
- //4. update the account record
- replaceFilter := bson.M{"_id": account.ID}
- err = sa.db.accounts.ReplaceOneWithContext(sessionContext, replaceFilter, account, nil)
- if err != nil {
- sa.abortTransaction(sessionContext)
- return errors.WrapErrorAction(logutils.ActionReplace, model.TypeAccount, nil, err)
- }
+ filter := bson.M{"_id": item.Account.ID, "auth_types.id": item.ID}
+ update := bson.D{
+ primitive.E{Key: "$set", Value: bson.D{
+ primitive.E{Key: "auth_types.$", Value: storageItem},
+ primitive.E{Key: "date_updated", Value: now},
+ }},
+ }
- //commit the transaction
- err = sessionContext.CommitTransaction(sessionContext)
- if err != nil {
- sa.abortTransaction(sessionContext)
- return errors.WrapErrorAction(logutils.ActionCommit, logutils.TypeTransaction, nil, err)
- }
- return nil
- })
+ res, err := sa.db.accounts.UpdateOneWithContext(context, filter, update, nil)
if err != nil {
- return err
+ return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeAccountAuthType, nil, err)
+ }
+ if res.ModifiedCount != 1 {
+ return errors.ErrorAction(logutils.ActionUpdate, model.TypeAccount, &logutils.FieldArgs{"modified": res.ModifiedCount, "expected": 1})
}
return nil
@@ -2312,7 +2338,10 @@ func (sa *Adapter) DeleteAccountAuthType(context TransactionContext, item model.
filter := bson.M{"_id": item.Account.ID}
update := bson.D{
primitive.E{Key: "$pull", Value: bson.D{
- primitive.E{Key: "auth_types", Value: bson.M{"auth_type_code": item.AuthType.Code, "identifier": item.Identifier}},
+ primitive.E{Key: "auth_types", Value: bson.M{"id": item.ID, "auth_type_code": item.SupportedAuthType.AuthType.Code}},
+ }},
+ primitive.E{Key: "$set", Value: bson.D{
+ primitive.E{Key: "date_updated", Value: time.Now().UTC()},
}},
}
@@ -2327,42 +2356,125 @@ func (sa *Adapter) DeleteAccountAuthType(context TransactionContext, item model.
return nil
}
-// UpdateAccountExternalIDs updates account external IDs
-func (sa *Adapter) UpdateAccountExternalIDs(accountID string, externalIDs map[string]string) error {
- filter := bson.D{primitive.E{Key: "_id", Value: accountID}}
- now := time.Now().UTC()
+// InsertAccountIdentifier inserts am account auth type
+func (sa *Adapter) InsertAccountIdentifier(context TransactionContext, item model.AccountIdentifier) error {
+ storageItem := accountIdentifierToStorage(item)
+
+ //3. first find the account record
+ filter := bson.M{"_id": item.Account.ID}
update := bson.D{
+ primitive.E{Key: "$push", Value: bson.D{
+ primitive.E{Key: "identifiers", Value: storageItem},
+ }},
primitive.E{Key: "$set", Value: bson.D{
- primitive.E{Key: "external_ids", Value: externalIDs},
- primitive.E{Key: "date_updated", Value: &now},
+ primitive.E{Key: "date_updated", Value: time.Now().UTC()},
}},
}
- res, err := sa.db.accounts.UpdateOne(filter, update, nil)
+ res, err := sa.db.accounts.UpdateOneWithContext(context, filter, update, nil)
if err != nil {
- return errors.WrapErrorAction(logutils.ActionUpdate, "account external IDs", &logutils.FieldArgs{"_id": accountID}, err)
+ return errors.WrapErrorAction(logutils.ActionInsert, model.TypeAccountIdentifier, nil, err)
}
if res.ModifiedCount != 1 {
- return errors.ErrorAction(logutils.ActionUpdate, "account external IDs", &logutils.FieldArgs{"_id": accountID, "unexpected modified count": res.ModifiedCount})
+ return errors.ErrorAction(logutils.ActionUpdate, model.TypeAccount, &logutils.FieldArgs{"unexpected modified count": res.ModifiedCount})
}
return nil
}
-// UpdateLoginSessionExternalIDs updates login session external IDs
-func (sa *Adapter) UpdateLoginSessionExternalIDs(accountID string, externalIDs map[string]string) error {
- filter := bson.D{primitive.E{Key: "identifier", Value: accountID}}
+// UpdateAccountIdentifier updates an account with the given account identifier
+func (sa *Adapter) UpdateAccountIdentifier(context TransactionContext, item model.AccountIdentifier) error {
+ storageItem := accountIdentifierToStorage(item)
now := time.Now().UTC()
+ storageItem.DateUpdated = &now
+
+ filter := bson.M{"_id": item.Account.ID, "identifiers.id": item.ID}
update := bson.D{
primitive.E{Key: "$set", Value: bson.D{
- primitive.E{Key: "external_ids", Value: externalIDs},
- primitive.E{Key: "date_updated", Value: &now},
+ primitive.E{Key: "identifiers.$", Value: storageItem},
+ primitive.E{Key: "date_updated", Value: now},
+ }},
+ }
+
+ res, err := sa.db.accounts.UpdateOneWithContext(context, filter, update, nil)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeAccountIdentifier, nil, err)
+ }
+ if res.ModifiedCount != 1 {
+ return errors.ErrorAction(logutils.ActionUpdate, model.TypeAccount, &logutils.FieldArgs{"modified": res.ModifiedCount, "expected": 1})
+ }
+
+ return nil
+}
+
+// UpdateAccountIdentifiers updates an account with the given list of account identifiers
+func (sa *Adapter) UpdateAccountIdentifiers(context TransactionContext, accountID string, items []model.AccountIdentifier) error {
+ if len(items) == 0 {
+ return nil
+ }
+
+ storageItems := accountIdentifiersToStorage(items)
+
+ filter := bson.M{"_id": accountID}
+ update := bson.D{
+ primitive.E{Key: "$set", Value: bson.D{
+ primitive.E{Key: "identifiers", Value: storageItems},
+ primitive.E{Key: "date_updated", Value: time.Now().UTC()},
+ }},
+ }
+
+ res, err := sa.db.accounts.UpdateOneWithContext(context, filter, update, nil)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeAccountIdentifier, nil, err)
+ }
+ if res.ModifiedCount != 1 {
+ return errors.ErrorAction(logutils.ActionUpdate, model.TypeAccount, &logutils.FieldArgs{"modified": res.ModifiedCount, "expected": 1})
+ }
+
+ return nil
+}
+
+// DeleteAccountIdentifier deletes the given account identifier from an account
+func (sa *Adapter) DeleteAccountIdentifier(context TransactionContext, item model.AccountIdentifier) error {
+ filter := bson.M{"_id": item.Account.ID}
+ update := bson.D{
+ primitive.E{Key: "$pull", Value: bson.D{
+ primitive.E{Key: "identifiers", Value: bson.M{"id": item.ID, "code": item.Code}},
+ }},
+ primitive.E{Key: "$set", Value: bson.D{
+ primitive.E{Key: "date_updated", Value: time.Now().UTC()},
}},
}
- _, err := sa.db.loginsSessions.UpdateMany(filter, update, nil)
+ res, err := sa.db.accounts.UpdateOneWithContext(context, filter, update, nil)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionDelete, model.TypeAccountIdentifier, nil, err)
+ }
+ if res.ModifiedCount != 1 {
+ return errors.ErrorAction(logutils.ActionUpdate, model.TypeAccount, &logutils.FieldArgs{"modified": res.ModifiedCount, "expected": 1})
+ }
+
+ return nil
+}
+
+// DeleteExternalAccountIdentifiers deletes account identifiers with an account auth type ID matching the given account auth type
+func (sa *Adapter) DeleteExternalAccountIdentifiers(context TransactionContext, aat model.AccountAuthType) error {
+ filter := bson.M{"_id": aat.Account.ID}
+ update := bson.D{
+ primitive.E{Key: "$pull", Value: bson.D{
+ primitive.E{Key: "identifiers", Value: bson.M{"account_auth_type_id": aat.ID}},
+ }},
+ primitive.E{Key: "$set", Value: bson.D{
+ primitive.E{Key: "date_updated", Value: time.Now().UTC()},
+ }},
+ }
+
+ res, err := sa.db.accounts.UpdateOne(filter, update, nil)
if err != nil {
- return errors.WrapErrorAction(logutils.ActionUpdate, "login session external IDs", &logutils.FieldArgs{"identifier": accountID}, err)
+ return errors.WrapErrorAction(logutils.ActionDelete, model.TypeAccountIdentifier, nil, err)
+ }
+ if res.ModifiedCount != 1 {
+ return errors.ErrorAction(logutils.ActionUpdate, model.TypeAccount, &logutils.FieldArgs{"modified": res.ModifiedCount, "expected": 1})
}
return nil
@@ -2407,14 +2519,37 @@ func (sa *Adapter) FindCredential(context TransactionContext, ID string) (*model
return &modelCreds, nil
}
+// FindCredentials finds a list of credentials by a list of IDs
+func (sa *Adapter) FindCredentials(context TransactionContext, ids []string) ([]model.Credential, error) {
+ filter := bson.D{primitive.E{Key: "_id", Value: bson.M{"$in": ids}}}
+
+ var creds []credential
+ err := sa.db.credentials.FindWithContext(context, filter, &creds, nil)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeCredential, &logutils.FieldArgs{"ids": ids}, err)
+ }
+
+ return credentialsFromStorage(creds), nil
+}
+
// InsertCredential inserts a set of credential
func (sa *Adapter) InsertCredential(context TransactionContext, creds *model.Credential) error {
- storageCreds := credentialToStorage(creds)
-
- if storageCreds == nil {
+ if creds == nil {
return errors.ErrorData(logutils.StatusInvalid, logutils.TypeArg, logutils.StringArgs(model.TypeCredential))
}
+ if creds.AuthType.ID == "" {
+ authType, err := sa.getCachedAuthType(creds.AuthType.Code)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionLoadCache, model.TypeAuthType, &logutils.FieldArgs{"code": creds.AuthType.Code}, err)
+ }
+ if authType == nil {
+ return errors.ErrorData(logutils.StatusMissing, model.TypeAuthType, &logutils.FieldArgs{"code": creds.AuthType.Code})
+ }
+ creds.AuthType = *authType
+ }
+ storageCreds := credentialToStorage(*creds)
+
_, err := sa.db.credentials.InsertOneWithContext(context, storageCreds)
if err != nil {
return errors.WrapErrorAction(logutils.ActionInsert, model.TypeCredential, nil, err)
@@ -2425,12 +2560,12 @@ func (sa *Adapter) InsertCredential(context TransactionContext, creds *model.Cre
// UpdateCredential updates a set of credentials
func (sa *Adapter) UpdateCredential(context TransactionContext, creds *model.Credential) error {
- storageCreds := credentialToStorage(creds)
-
- if storageCreds == nil {
+ if creds == nil {
return errors.ErrorData(logutils.StatusInvalid, logutils.TypeArg, logutils.StringArgs(model.TypeCredential))
}
+ storageCreds := credentialToStorage(*creds)
+
filter := bson.D{primitive.E{Key: "_id", Value: storageCreds.ID}}
err := sa.db.credentials.ReplaceOneWithContext(context, filter, storageCreds, nil)
if err != nil {
@@ -2446,6 +2581,7 @@ func (sa *Adapter) UpdateCredentialValue(ID string, value map[string]interface{}
update := bson.D{
primitive.E{Key: "$set", Value: bson.D{
primitive.E{Key: "value", Value: value},
+ primitive.E{Key: "date_updated", Value: time.Now().UTC()},
}},
}
@@ -3184,8 +3320,6 @@ func (sa *Adapter) UpdateAccountProfile(context TransactionContext, profile mode
primitive.E{Key: "profile.photo_url", Value: profile.PhotoURL},
primitive.E{Key: "profile.first_name", Value: profile.FirstName},
primitive.E{Key: "profile.last_name", Value: profile.LastName},
- primitive.E{Key: "profile.email", Value: profile.Email},
- primitive.E{Key: "profile.phone", Value: profile.Phone},
primitive.E{Key: "profile.birth_year", Value: profile.BirthYear},
primitive.E{Key: "profile.address", Value: profile.Address},
primitive.E{Key: "profile.zip_code", Value: profile.ZipCode},
@@ -3224,10 +3358,10 @@ func (sa *Adapter) UpdateAccountPrivacy(context TransactionContext, accountID st
return nil
}
-// FindAccountProfiles finds profiles by app id, authtype id and account auth type identifier
-func (sa *Adapter) FindAccountProfiles(appID string, authTypeID string, accountAuthTypeIdentifier string) ([]model.Profile, error) {
+// FindAccountProfiles finds profiles by app id, authtype id and account identifier
+func (sa *Adapter) FindAccountProfiles(appID string, accountIdentifier string) ([]model.Profile, error) {
pipeline := []bson.M{
- {"$match": bson.M{"auth_types.auth_type_id": authTypeID, "auth_types.identifier": accountAuthTypeIdentifier}},
+ {"$match": bson.M{"identifiers.identifier": accountIdentifier}},
{"$lookup": bson.M{
"from": "applications_organizations",
"localField": "app_org_id",
@@ -3239,14 +3373,14 @@ func (sa *Adapter) FindAccountProfiles(appID string, authTypeID string, accountA
var accounts []account
err := sa.db.accounts.Aggregate(pipeline, &accounts, nil)
if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, &logutils.FieldArgs{"app_id": appID, "auth_types.id": authTypeID, "auth_types.identifier": accountAuthTypeIdentifier}, err)
+ return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, &logutils.FieldArgs{"app_id": appID, "identifiers.identifier": accountIdentifier}, err)
}
if len(accounts) == 0 {
//not found
return nil, nil
}
- result := profilesFromStorage(accounts, *sa)
+ result := profilesFromStorage(accounts, sa)
return result, nil
}
@@ -3279,8 +3413,8 @@ func (sa *Adapter) FindConfigs(configType *string) ([]model.Config, error) {
}
// InsertConfig inserts a new config
-func (sa *Adapter) InsertConfig(config model.Config) error {
- _, err := sa.db.configs.InsertOne(config)
+func (sa *Adapter) InsertConfig(context TransactionContext, config model.Config) error {
+ _, err := sa.db.configs.InsertOneWithContext(context, config)
if err != nil {
return errors.WrapErrorAction(logutils.ActionInsert, model.TypeConfig, nil, err)
}
@@ -3289,7 +3423,7 @@ func (sa *Adapter) InsertConfig(config model.Config) error {
}
// UpdateConfig updates an existing config
-func (sa *Adapter) UpdateConfig(config model.Config) error {
+func (sa *Adapter) UpdateConfig(context TransactionContext, config model.Config) error {
filter := bson.M{"_id": config.ID}
update := bson.D{
primitive.E{Key: "$set", Value: bson.D{
@@ -3298,10 +3432,10 @@ func (sa *Adapter) UpdateConfig(config model.Config) error {
primitive.E{Key: "org_id", Value: config.OrgID},
primitive.E{Key: "system", Value: config.System},
primitive.E{Key: "data", Value: config.Data},
- primitive.E{Key: "date_updated", Value: config.DateUpdated},
+ primitive.E{Key: "date_updated", Value: time.Now().UTC()},
}},
}
- _, err := sa.db.configs.UpdateOne(filter, update, nil)
+ _, err := sa.db.configs.UpdateOneWithContext(context, filter, update, nil)
if err != nil {
return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeConfig, &logutils.FieldArgs{"id": config.ID}, err)
}
@@ -3361,7 +3495,7 @@ func (sa *Adapter) InsertOrganization(context TransactionContext, organization m
// UpdateOrganization updates an organization
func (sa *Adapter) UpdateOrganization(ID string, name string, requestType string, organizationDomains []string) error {
- now := time.Now()
+ now := time.Now().UTC()
//TODO - use pointers and update only what not nil
updatOrganizationFilter := bson.D{primitive.E{Key: "_id", Value: ID}}
updateOrganization := bson.D{
@@ -3538,7 +3672,7 @@ func (sa *Adapter) InsertAppConfig(item model.ApplicationConfig) (*model.Applica
// UpdateAppConfig updates an appconfig
func (sa *Adapter) UpdateAppConfig(ID string, appType model.ApplicationType, appOrg *model.ApplicationOrganization, version model.Version, data map[string]interface{}) error {
- now := time.Now()
+ now := time.Now().UTC()
//TODO - use pointers and update only what not nil
updatAppConfigFilter := bson.D{primitive.E{Key: "_id", Value: ID}}
updateItem := bson.D{primitive.E{Key: "date_updated", Value: now}, primitive.E{Key: "app_type_id", Value: appType.ID}, primitive.E{Key: "version", Value: versionToStorage(version)}}
@@ -3714,7 +3848,7 @@ func (sa *Adapter) InsertApplicationOrganization(context TransactionContext, app
// UpdateApplicationOrganization updates an application organization
func (sa *Adapter) UpdateApplicationOrganization(context TransactionContext, applicationOrganization model.ApplicationOrganization) error {
appOrg := applicationOrganizationToStorage(applicationOrganization)
- now := time.Now()
+ now := time.Now().UTC()
filter := bson.M{"_id": applicationOrganization.ID}
update := bson.D{primitive.E{Key: "date_updated", Value: now},
@@ -3806,7 +3940,7 @@ func (sa *Adapter) InsertAuthType(context TransactionContext, authType model.Aut
func (sa *Adapter) UpdateAuthTypes(ID string, code string, description string, isExternal bool, isAnonymous bool,
useCredentials bool, ignoreMFA bool, params map[string]interface{}) error {
- now := time.Now()
+ now := time.Now().UTC()
updateAuthTypeFilter := bson.D{primitive.E{Key: "_id", Value: ID}}
updateAuthType := bson.D{
primitive.E{Key: "$set", Value: bson.D{
diff --git a/driven/storage/conversions_auth.go b/driven/storage/conversions_auth.go
index 55c789887..ecebed7ed 100644
--- a/driven/storage/conversions_auth.go
+++ b/driven/storage/conversions_auth.go
@@ -31,11 +31,6 @@ func loginSessionFromStorage(item loginSession, authType model.AuthType, account
anonymous := item.Anonymous
identifier := item.Identifier
- externalIDs := item.ExternalIDs
- var accountAuthType *model.AccountAuthType
- if item.AccountAuthTypeID != nil && account != nil {
- accountAuthType = account.GetAccountAuthTypeByID(*item.AccountAuthTypeID)
- }
var deviceID string
if item.DeviceID != nil {
deviceID = *item.DeviceID
@@ -61,10 +56,9 @@ func loginSessionFromStorage(item loginSession, authType model.AuthType, account
dateUpdated := item.DateUpdated
dateCreated := item.DateCreated
- return model.LoginSession{ID: id, AppOrg: appOrg, AuthType: authType, AppType: appType,
- Anonymous: anonymous, Identifier: identifier, ExternalIDs: externalIDs, AccountAuthType: accountAuthType,
- Device: device, IPAddress: idAddress, AccessToken: accessToken, RefreshTokens: refreshTokens, Params: params,
- State: state, StateExpires: stateExpires, MfaAttempts: mfaAttempts,
+ return model.LoginSession{ID: id, AppOrg: appOrg, AuthType: authType, AppType: appType, Anonymous: anonymous,
+ Identifier: identifier, Account: account, Device: device, IPAddress: idAddress, AccessToken: accessToken,
+ RefreshTokens: refreshTokens, Params: params, State: state, StateExpires: stateExpires, MfaAttempts: mfaAttempts,
DateRefreshed: dateRefreshed, DateUpdated: dateUpdated, DateCreated: dateCreated}
}
@@ -81,13 +75,6 @@ func loginSessionToStorage(item model.LoginSession) *loginSession {
anonymous := item.Anonymous
identifier := item.Identifier
- externalIDs := item.ExternalIDs
- var accountAuthTypeID *string
- var accountAuthTypeIdentifier *string
- if item.AccountAuthType != nil && len(item.AccountAuthType.ID) != 0 {
- accountAuthTypeID = &item.AccountAuthType.ID
- accountAuthTypeIdentifier = &item.AccountAuthType.Identifier
- }
var deviceID *string
if item.Device != nil {
deviceID = &item.Device.ID
@@ -112,12 +99,10 @@ func loginSessionToStorage(item model.LoginSession) *loginSession {
dateUpdated := item.DateUpdated
dateCreated := item.DateCreated
- return &loginSession{ID: id, AppID: appID, OrgID: orgID, AuthTypeCode: authTypeCode,
- AppTypeID: appTypeID, AppTypeIdentifier: appTypeIdentifier, Anonymous: anonymous,
- Identifier: identifier, ExternalIDs: externalIDs, AccountAuthTypeID: accountAuthTypeID, AccountAuthTypeIdentifier: accountAuthTypeIdentifier,
- DeviceID: deviceID, IPAddress: ipAddress, AccessToken: accessToken, RefreshTokens: refreshTokens,
- Params: params, State: state, StateExpires: stateExpires, MfaAttempts: mfaAttempts,
- DateRefreshed: dateRefreshed, DateUpdated: dateUpdated, DateCreated: dateCreated}
+ return &loginSession{ID: id, AppID: appID, OrgID: orgID, AuthTypeCode: authTypeCode, AppTypeID: appTypeID, AppTypeIdentifier: appTypeIdentifier,
+ Anonymous: anonymous, Identifier: identifier, DeviceID: deviceID, IPAddress: ipAddress, AccessToken: accessToken, RefreshTokens: refreshTokens,
+ Params: params, State: state, StateExpires: stateExpires, MfaAttempts: mfaAttempts, DateRefreshed: dateRefreshed,
+ DateUpdated: dateUpdated, DateCreated: dateCreated}
}
// ServiceAccount
diff --git a/driven/storage/conversions_user.go b/driven/storage/conversions_user.go
index e05fedc62..10090fe66 100644
--- a/driven/storage/conversions_user.go
+++ b/driven/storage/conversions_user.go
@@ -19,27 +19,28 @@ import (
)
// Account
-func accountFromStorage(item account, appOrg model.ApplicationOrganization) model.Account {
+func accountFromStorage(item account, appOrg model.ApplicationOrganization, sa *Adapter) model.Account {
roles := accountRolesFromStorage(item.Roles, appOrg)
groups := accountGroupsFromStorage(item.Groups, appOrg)
- authTypes := accountAuthTypesFromStorage(item.AuthTypes)
+ identifiers := accountIdentifiersFromStorage(item.Identifiers)
+ authTypes := accountAuthTypesFromStorage(item.AuthTypes, sa)
mfaTypes := mfaTypesFromStorage(item.MFATypes)
profile := profileFromStorage(item.Profile)
devices := accountDevicesFromStorage(item)
- return model.Account{ID: item.ID, AppOrg: appOrg, Anonymous: item.Anonymous, Permissions: item.Permissions, Roles: roles, Groups: groups, Scopes: item.Scopes, AuthTypes: authTypes,
- MFATypes: mfaTypes, Username: item.Username, ExternalIDs: item.ExternalIDs, Preferences: item.Preferences, Profile: profile, SystemConfigs: item.SystemConfigs,
- Privacy: item.Privacy, Verified: item.Verified, Devices: devices, DateCreated: item.DateCreated, DateUpdated: item.DateUpdated, LastLoginDate: item.LastLoginDate,
- LastAccessTokenDate: item.LastAccessTokenDate, MostRecentClientVersion: item.MostRecentClientVersion}
+ return model.Account{ID: item.ID, AppOrg: appOrg, Anonymous: item.Anonymous, Permissions: item.Permissions, Roles: roles, Groups: groups, Scopes: item.Scopes,
+ Identifiers: identifiers, AuthTypes: authTypes, MFATypes: mfaTypes, Preferences: item.Preferences, Profile: profile, SystemConfigs: item.SystemConfigs,
+ Privacy: item.Privacy, Verified: item.Verified, Devices: devices, DateCreated: item.DateCreated, DateUpdated: item.DateUpdated,
+ LastLoginDate: item.LastLoginDate, LastAccessTokenDate: item.LastAccessTokenDate, MostRecentClientVersion: item.MostRecentClientVersion}
}
-func accountsFromStorage(items []account, appOrg model.ApplicationOrganization) []model.Account {
+func accountsFromStorage(items []account, appOrg model.ApplicationOrganization, sa *Adapter) []model.Account {
if len(items) == 0 {
return make([]model.Account, 0)
}
res := make([]model.Account, len(items))
for i, item := range items {
- res[i] = accountFromStorage(item, appOrg)
+ res[i] = accountFromStorage(item, appOrg, sa)
}
return res
}
@@ -50,6 +51,7 @@ func accountToStorage(item *model.Account) *account {
permissions := item.Permissions
roles := accountRolesToStorage(item.Roles)
groups := accountGroupsToStorage(item.Groups)
+ identifiers := accountIdentifiersToStorage(item.Identifiers)
authTypes := accountAuthTypesToStorage(item.AuthTypes)
mfaTypes := mfaTypesToStorage(item.MFATypes)
profile := profileToStorage(item.Profile)
@@ -60,9 +62,10 @@ func accountToStorage(item *model.Account) *account {
lastAccessTokenDate := item.LastAccessTokenDate
mostRecentClientVersion := item.MostRecentClientVersion
- return &account{ID: id, AppOrgID: appOrgID, Anonymous: item.Anonymous, Permissions: permissions, Roles: roles, Groups: groups, Scopes: item.Scopes, AuthTypes: authTypes, MFATypes: mfaTypes,
- Privacy: item.Privacy, Verified: item.Verified, Username: item.Username, ExternalIDs: item.ExternalIDs, Preferences: item.Preferences, Profile: profile, SystemConfigs: item.SystemConfigs, Devices: devices,
- DateCreated: dateCreated, DateUpdated: dateUpdated, LastLoginDate: lastLoginDate, LastAccessTokenDate: lastAccessTokenDate, MostRecentClientVersion: mostRecentClientVersion}
+ return &account{ID: id, AppOrgID: appOrgID, Anonymous: item.Anonymous, Permissions: permissions, Roles: roles, Groups: groups, Scopes: item.Scopes,
+ Identifiers: identifiers, AuthTypes: authTypes, MFATypes: mfaTypes, Privacy: item.Privacy, Verified: item.Verified, Preferences: item.Preferences,
+ Profile: profile, SystemConfigs: item.SystemConfigs, Devices: devices, DateCreated: dateCreated, DateUpdated: dateUpdated,
+ LastLoginDate: lastLoginDate, LastAccessTokenDate: lastAccessTokenDate, MostRecentClientVersion: mostRecentClientVersion}
}
func accountDevicesFromStorage(item account) []model.Device {
@@ -94,28 +97,24 @@ func accountDeviceToStorage(item model.Device) userDevice {
}
// AccountAuthType
-func accountAuthTypeFromStorage(item accountAuthType) model.AccountAuthType {
+func accountAuthTypeFromStorage(item accountAuthType, sa *Adapter) model.AccountAuthType {
id := item.ID
- authType := model.AuthType{ID: item.AuthTypeID, Code: item.AuthTypeCode}
- identifier := item.Identifier
+
+ authType, _ := sa.FindAuthType(item.AuthTypeID)
params := item.Params
var credential *model.Credential
if item.CredentialID != nil {
credential = &model.Credential{ID: *item.CredentialID}
}
active := item.Active
- return model.AccountAuthType{ID: id, AuthType: authType, Identifier: identifier, Params: params, Credential: credential,
- Active: active, Unverified: item.Unverified, Linked: item.Linked, DateCreated: item.DateCreated, DateUpdated: item.DateUpdated}
+ return model.AccountAuthType{ID: id, SupportedAuthType: model.SupportedAuthType{AuthTypeID: item.AuthTypeID, AuthType: *authType}, Params: params, Credential: credential,
+ Active: active, DateCreated: item.DateCreated, DateUpdated: item.DateUpdated}
}
-func accountAuthTypesFromStorage(items []accountAuthType) []model.AccountAuthType {
- if len(items) == 0 {
- return make([]model.AccountAuthType, 0)
- }
-
+func accountAuthTypesFromStorage(items []accountAuthType, sa *Adapter) []model.AccountAuthType {
res := make([]model.AccountAuthType, len(items))
for i, aat := range items {
- res[i] = accountAuthTypeFromStorage(aat)
+ res[i] = accountAuthTypeFromStorage(aat, sa)
}
return res
}
@@ -125,15 +124,11 @@ func accountAuthTypeToStorage(item model.AccountAuthType) accountAuthType {
if item.Credential != nil {
credentialID = &item.Credential.ID
}
- return accountAuthType{ID: item.ID, AuthTypeID: item.AuthType.ID, AuthTypeCode: item.AuthType.Code, Identifier: item.Identifier,
- Params: item.Params, CredentialID: credentialID, Active: item.Active, Unverified: item.Unverified, Linked: item.Linked, DateCreated: item.DateCreated, DateUpdated: item.DateUpdated}
+ return accountAuthType{ID: item.ID, AuthTypeID: item.SupportedAuthType.AuthType.ID, AuthTypeCode: item.SupportedAuthType.AuthType.Code,
+ Params: item.Params, CredentialID: credentialID, Active: item.Active, DateCreated: item.DateCreated, DateUpdated: item.DateUpdated}
}
func accountAuthTypesToStorage(items []model.AccountAuthType) []accountAuthType {
- if len(items) == 0 {
- return make([]accountAuthType, 0)
- }
-
res := make([]accountAuthType, len(items))
for i, aat := range items {
res[i] = accountAuthTypeToStorage(aat)
@@ -141,6 +136,35 @@ func accountAuthTypesToStorage(items []model.AccountAuthType) []accountAuthType
return res
}
+// AccountIdentifier
+func accountIdentifierFromStorage(item accountIdentifier) model.AccountIdentifier {
+ return model.AccountIdentifier{ID: item.ID, Code: item.Code, Identifier: item.Identifier, Verified: item.Verified, Linked: item.Linked,
+ Sensitive: item.Sensitive, AccountAuthTypeID: item.AccountAuthTypeID, Primary: item.Primary, VerificationCode: item.VerificationCode,
+ VerificationExpiry: item.VerificationExpiry, DateCreated: item.DateCreated, DateUpdated: item.DateUpdated}
+}
+
+func accountIdentifiersFromStorage(items []accountIdentifier) []model.AccountIdentifier {
+ res := make([]model.AccountIdentifier, len(items))
+ for i, aat := range items {
+ res[i] = accountIdentifierFromStorage(aat)
+ }
+ return res
+}
+
+func accountIdentifierToStorage(item model.AccountIdentifier) accountIdentifier {
+ return accountIdentifier{ID: item.ID, Code: item.Code, Identifier: item.Identifier, Verified: item.Verified, Linked: item.Linked,
+ Sensitive: item.Sensitive, AccountAuthTypeID: item.AccountAuthTypeID, Primary: item.Primary, VerificationCode: item.VerificationCode,
+ VerificationExpiry: item.VerificationExpiry, DateCreated: item.DateCreated, DateUpdated: item.DateUpdated}
+}
+
+func accountIdentifiersToStorage(items []model.AccountIdentifier) []accountIdentifier {
+ res := make([]accountIdentifier, len(items))
+ for i, aat := range items {
+ res[i] = accountIdentifierToStorage(aat)
+ }
+ return res
+}
+
// AccountRole
func accountRoleFromStorage(item *accountRole, appOrg model.ApplicationOrganization) model.AccountRole {
if item == nil {
@@ -222,12 +246,12 @@ func accountGroupsToStorage(items []model.AccountGroup) []accountGroup {
// Profile
func profileFromStorage(item profile) model.Profile {
return model.Profile{ID: item.ID, PhotoURL: item.PhotoURL, FirstName: item.FirstName, LastName: item.LastName,
- Email: item.Email, Phone: item.Phone, BirthYear: item.BirthYear, Address: item.Address, ZipCode: item.ZipCode,
- State: item.State, Country: item.Country, DateCreated: item.DateCreated, DateUpdated: item.DateUpdated,
+ BirthYear: item.BirthYear, Address: item.Address, ZipCode: item.ZipCode, State: item.State,
+ Country: item.Country, DateCreated: item.DateCreated, DateUpdated: item.DateUpdated,
UnstructuredProperties: item.UnstructuredProperties}
}
-func profilesFromStorage(items []account, sa Adapter) []model.Profile {
+func profilesFromStorage(items []account, sa *Adapter) []model.Profile {
if len(items) == 0 {
return make([]model.Profile, 0)
}
@@ -236,7 +260,7 @@ func profilesFromStorage(items []account, sa Adapter) []model.Profile {
accounts := make(map[string][]model.Account, len(items))
for _, account := range items {
appOrg, _ := sa.getCachedApplicationOrganizationByKey(account.AppOrgID)
- rAccount := accountFromStorage(account, *appOrg)
+ rAccount := accountFromStorage(account, *appOrg, sa)
//add account to the map
profileAccounts := accounts[rAccount.Profile.ID]
@@ -261,8 +285,8 @@ func profilesFromStorage(items []account, sa Adapter) []model.Profile {
func profileToStorage(item model.Profile) profile {
return profile{ID: item.ID, PhotoURL: item.PhotoURL, FirstName: item.FirstName, LastName: item.LastName,
- Email: item.Email, Phone: item.Phone, BirthYear: item.BirthYear, Address: item.Address, ZipCode: item.ZipCode,
- State: item.State, Country: item.Country, DateCreated: item.DateCreated, DateUpdated: item.DateUpdated,
+ BirthYear: item.BirthYear, Address: item.Address, ZipCode: item.ZipCode, State: item.State,
+ Country: item.Country, DateCreated: item.DateCreated, DateUpdated: item.DateUpdated,
UnstructuredProperties: item.UnstructuredProperties}
}
@@ -287,23 +311,35 @@ func credentialFromStorage(item credential) model.Credential {
accountAuthTypes[i] = model.AccountAuthType{ID: id}
}
authType := model.AuthType{ID: item.AuthTypeID}
- return model.Credential{ID: item.ID, AuthType: authType, AccountsAuthTypes: accountAuthTypes, Verified: item.Verified,
+ return model.Credential{ID: item.ID, AuthType: authType, AccountsAuthTypes: accountAuthTypes,
Value: item.Value, DateCreated: item.DateCreated, DateUpdated: item.DateUpdated}
}
-func credentialToStorage(item *model.Credential) *credential {
- if item == nil {
- return nil
+func credentialsFromStorage(items []credential) []model.Credential {
+ res := make([]model.Credential, len(items))
+ for i, cred := range items {
+ res[i] = credentialFromStorage(cred)
}
+ return res
+}
+func credentialToStorage(item model.Credential) credential {
accountAuthTypes := make([]string, len(item.AccountsAuthTypes))
for i, aat := range item.AccountsAuthTypes {
accountAuthTypes[i] = aat.ID
}
- return &credential{ID: item.ID, AuthTypeID: item.AuthType.ID, AccountsAuthTypes: accountAuthTypes, Verified: item.Verified,
+ return credential{ID: item.ID, AuthTypeID: item.AuthType.ID, AccountsAuthTypes: accountAuthTypes,
Value: item.Value, DateCreated: item.DateCreated, DateUpdated: item.DateUpdated}
}
+func credentialsToStorage(items []model.Credential) []credential {
+ res := make([]credential, len(items))
+ for i, cred := range items {
+ res[i] = credentialToStorage(cred)
+ }
+ return res
+}
+
// MFA
func mfaTypesFromStorage(items []mfaType) []model.MFAType {
res := make([]model.MFAType, len(items))
diff --git a/driven/storage/database.go b/driven/storage/database.go
index e2054a4f3..1cff2055b 100644
--- a/driven/storage/database.go
+++ b/driven/storage/database.go
@@ -42,6 +42,7 @@ type database struct {
devices *collectionWrapper
credentials *collectionWrapper
loginsSessions *collectionWrapper
+ loginStates *collectionWrapper
configs *collectionWrapper
serviceRegs *collectionWrapper
serviceRegistrations *collectionWrapper
@@ -136,6 +137,12 @@ func (m *database) start() error {
return err
}
+ loginStates := &collectionWrapper{database: m, coll: db.Collection("login_states")}
+ err = m.applyLoginStatesChecks(loginStates)
+ if err != nil {
+ return err
+ }
+
serviceAuthorizations := &collectionWrapper{database: m, coll: db.Collection("service_authorizations")}
err = m.applyServiceAuthorizationsChecks(serviceAuthorizations)
if err != nil {
@@ -212,6 +219,7 @@ func (m *database) start() error {
m.devices = devices
m.credentials = credentials
m.loginsSessions = loginsSessions
+ m.loginStates = loginStates
m.configs = configs
m.apiKeys = apiKeys
m.serviceRegs = serviceRegs
@@ -245,6 +253,11 @@ func (m *database) start() error {
func (m *database) applyAuthTypesChecks(authenticationTypes *collectionWrapper) error {
m.logger.Info("apply auth types checks.....")
+ err := authenticationTypes.AddIndex(bson.D{primitive.E{Key: "code", Value: 1}}, true)
+ if err != nil {
+ return err
+ }
+
m.logger.Info("auth types check passed")
return nil
}
@@ -259,9 +272,13 @@ func (m *database) applyIdentityProvidersChecks(identityProviders *collectionWra
func (m *database) applyAccountsChecks(accounts *collectionWrapper) error {
m.logger.Info("apply accounts checks.....")
- //add compound index - auth_type identifier + auth_type_id
+ // remove old indexes
+ accounts.DropIndex("auth_types.identifier_1_auth_types.auth_type_id_1_app_org_id_1")
+ accounts.DropIndex("auth_types.identifier_1_auth_types.auth_type_code_1_app_org_id_1")
+
+ //add compound index - identifier identifier + identifier code
// Can't be unique because of anonymous accounts
- err := accounts.AddIndex(bson.D{primitive.E{Key: "auth_types.identifier", Value: 1}, primitive.E{Key: "auth_types.auth_type_id", Value: 1}, primitive.E{Key: "app_org_id", Value: 1}}, false)
+ err := accounts.AddIndex(bson.D{primitive.E{Key: "identifiers.identifier", Value: 1}, primitive.E{Key: "identifiers.code", Value: 1}, primitive.E{Key: "app_org_id", Value: 1}}, false)
if err != nil {
return err
}
@@ -284,6 +301,12 @@ func (m *database) applyAccountsChecks(accounts *collectionWrapper) error {
return err
}
+ //add identifiers index
+ err = accounts.AddIndex(bson.D{primitive.E{Key: "identifiers.id", Value: 1}}, false)
+ if err != nil {
+ return err
+ }
+
// err = accounts.AddIndex(bson.D{primitive.E{Key: "username", Value: "text"}, primitive.E{Key: "profile.first_name", Value: "text"}, primitive.E{Key: "profile.last_name", Value: "text"}}, false)
// if err != nil {
// return err
@@ -309,25 +332,22 @@ func (m *database) applyDevicesChecks(devices *collectionWrapper) error {
func (m *database) applyCredentialChecks(credentials *collectionWrapper) error {
m.logger.Info("apply credentials checks.....")
- // Add user_auth_type_id index
- err := credentials.AddIndex(bson.D{primitive.E{Key: "user_auth_type_id", Value: 1}}, false)
- if err != nil {
- return err
- }
+ // remove unused index
+ credentials.DropIndex("user_auth_type_id_1")
m.logger.Info("credentials check passed")
return nil
}
-func (m *database) applyLoginsSessionsChecks(refreshTokens *collectionWrapper) error {
+func (m *database) applyLoginsSessionsChecks(loginSessions *collectionWrapper) error {
m.logger.Info("apply logins sessions checks.....")
- err := refreshTokens.AddIndex(bson.D{primitive.E{Key: "refresh_token", Value: 1}}, false)
+ err := loginSessions.AddIndex(bson.D{primitive.E{Key: "refresh_token", Value: 1}}, false)
if err != nil {
return err
}
- err = refreshTokens.AddIndex(bson.D{primitive.E{Key: "expires", Value: 1}}, false)
+ err = loginSessions.AddIndex(bson.D{primitive.E{Key: "expires", Value: 1}}, false)
if err != nil {
return err
}
@@ -336,6 +356,31 @@ func (m *database) applyLoginsSessionsChecks(refreshTokens *collectionWrapper) e
return nil
}
+func (m *database) applyLoginStatesChecks(loginStates *collectionWrapper) error {
+ m.logger.Info("apply logins states checks.....")
+
+ err := loginStates.AddIndex(bson.D{primitive.E{Key: "app_id", Value: 1}, primitive.E{Key: "org_id", Value: 1}}, false)
+ if err != nil {
+ return err
+ }
+
+ err = loginStates.AddIndex(bson.D{primitive.E{Key: "account_id", Value: 1}}, false)
+ if err != nil {
+ return err
+ }
+
+ // create TTL index which auto-deletes login state documents after 5 minutes
+ opts := options.IndexOptions{}
+ opts.SetExpireAfterSeconds(5 * 60)
+ err = loginStates.AddIndexWithOptions(bson.D{primitive.E{Key: "date_created", Value: 1}}, &opts)
+ if err != nil {
+ return err
+ }
+
+ m.logger.Info("logins states check passed")
+ return nil
+}
+
func (m *database) applyAPIKeysChecks(apiKeys *collectionWrapper) error {
m.logger.Info("apply api keys checks.....")
@@ -427,8 +472,6 @@ func (m *database) applyOrganizationsChecks(organizations *collectionWrapper) er
return err
}
- //TODO
-
//add applications index
err = organizations.AddIndex(bson.D{primitive.E{Key: "applications", Value: 1}}, false)
if err != nil {
diff --git a/driven/storage/migrations.go b/driven/storage/migrations.go
new file mode 100644
index 000000000..540e7bf94
--- /dev/null
+++ b/driven/storage/migrations.go
@@ -0,0 +1,473 @@
+// Copyright 2022 Board of Trustees of the University of Illinois.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package storage
+
+import (
+ "core-building-block/core/model"
+ "core-building-block/utils"
+ "strings"
+ "time"
+
+ "github.com/google/uuid"
+ "github.com/rokwire/logging-library-go/v2/errors"
+ "github.com/rokwire/logging-library-go/v2/logutils"
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+// Database Migration functions
+
+func (sa *Adapter) migrateAuthTypes() error {
+ transaction := func(context TransactionContext) error {
+ //1. insert new auth types
+ newAuthTypes := map[string]model.AuthType{
+ "password": {ID: uuid.NewString(), Code: "password", Description: "Authentication type relying on password", UseCredentials: true, Aliases: []string{"email", "username"}},
+ "code": {ID: uuid.NewString(), Code: "code", Description: "Authentication type relying on codes sent over a communication channel", Aliases: []string{"phone", "twilio_phone"}},
+ "webauthn": {ID: uuid.NewString(), Code: "webauthn", Description: "Authentication type relying on WebAuthn", UseCredentials: true},
+ }
+ inserted := false
+ for code, authType := range newAuthTypes {
+ existing, err := sa.FindAuthType(code)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionFind, model.TypeAuthType, logutils.StringArgs(code), err)
+ }
+ if existing == nil {
+ _, err = sa.InsertAuthType(context, authType)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionInsert, model.TypeAuthType, logutils.StringArgs(code), err)
+ }
+
+ inserted = true
+ }
+ }
+ // if all already exist, migration is done so return no error
+ if !inserted {
+ return nil
+ }
+
+ //2. remove old auth types if they exist
+ removedAuthTypeIDs := make(map[string]model.AuthType)
+ removedAuthTypeCodes := map[string]model.AuthType{
+ "email": newAuthTypes["password"],
+ "username": newAuthTypes["password"],
+ "phone": newAuthTypes["code"],
+ "twilio_phone": newAuthTypes["code"],
+ }
+ for old, new := range removedAuthTypeCodes {
+ // need to load auth type directly from DB so that we do not get one of the new auth types by alias
+ var authTypes []model.AuthType
+ err := sa.db.authTypes.FindWithContext(context, bson.M{"code": old}, &authTypes, nil)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionFind, model.TypeAuthType, logutils.StringArgs(old), err)
+ }
+ if len(authTypes) == 0 {
+ continue
+ }
+
+ removedAuthTypeIDs[authTypes[0].ID] = new
+
+ // remove the unwanted auth type, which also updates the cache
+ _, err = sa.db.authTypes.DeleteOneWithContext(context, bson.M{"code": old}, nil)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionDelete, model.TypeAuthType, logutils.StringArgs(old), err)
+ }
+ }
+
+ //3. migrate credentials
+ removedCredentials, err := sa.migrateCredentials(context, removedAuthTypeIDs)
+ if err != nil {
+ return errors.WrapErrorAction("migrating", model.TypeCredential, nil, err)
+ }
+
+ //4. migrate app orgs
+ err = sa.migrateAppOrgs(context, removedAuthTypeIDs, removedCredentials)
+ if err != nil {
+ return errors.WrapErrorAction("migrating", model.TypeApplicationOrganization, nil, err)
+ }
+
+ //5. migrate login sessions
+ err = sa.migrateLoginSessions(context, removedAuthTypeCodes)
+ if err != nil {
+ return errors.WrapErrorAction("migrating", model.TypeLoginSession, nil, err)
+ }
+
+ return nil
+ }
+
+ return sa.PerformTransaction(transaction)
+}
+
+func (sa *Adapter) migrateCredentials(context TransactionContext, removedAuthTypes map[string]model.AuthType) ([]string, error) {
+ var allCredentials []credential
+ err := sa.db.credentials.FindWithContext(context, bson.M{}, &allCredentials, nil)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeCredential, nil, err)
+ }
+
+ type passwordCreds struct {
+ Password string `json:"password"`
+
+ ResetCode *string `json:"reset_code,omitempty"`
+ ResetExpiry *time.Time `json:"reset_expiry,omitempty"`
+ }
+
+ type webauthnCreds struct {
+ Credential *string `json:"credential,omitempty"`
+ Session *string `json:"session,omitempty"`
+ }
+
+ migratedCredentials := make([]interface{}, 0)
+ removedCredentials := make([]string, 0)
+ for _, cred := range allCredentials {
+ var migrated credential
+ if newAuthType, exists := removedAuthTypes[cred.AuthTypeID]; exists && newAuthType.Code == "password" {
+ // found a password credential, migrate it
+ passwordValue, err := utils.JSONConvert[passwordCreds, map[string]interface{}](cred.Value)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionParse, "password credential value map", &logutils.FieldArgs{"id": cred.ID}, err)
+ }
+ if passwordValue == nil || passwordValue.Password == "" {
+ removedCredentials = append(removedCredentials, cred.ID)
+ continue
+ }
+ if passwordValue.ResetCode != nil && *passwordValue.ResetCode == "" {
+ passwordValue.ResetCode = nil
+ }
+ if passwordValue.ResetExpiry != nil && passwordValue.ResetExpiry.IsZero() {
+ passwordValue.ResetExpiry = nil
+ }
+
+ passwordValueMap, err := utils.JSONConvert[map[string]interface{}, passwordCreds](*passwordValue)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionParse, "password credential value", &logutils.FieldArgs{"id": cred.ID}, err)
+ }
+ if passwordValueMap == nil {
+ removedCredentials = append(removedCredentials, cred.ID)
+ continue
+ }
+
+ migrated = credential{ID: cred.ID, AuthTypeID: newAuthType.ID, AccountsAuthTypes: cred.AccountsAuthTypes, Value: *passwordValueMap,
+ DateCreated: cred.DateCreated, DateUpdated: cred.DateUpdated}
+ } else {
+ // found something other than a password credential, try to migrate it as a webauthn credential
+ webauthnValue, err := utils.JSONConvert[webauthnCreds, map[string]interface{}](cred.Value)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionParse, "webauthn credential value map", &logutils.FieldArgs{"id": cred.ID}, err)
+ }
+
+ // credential value is not for webauthn or it is in a hanging state or is missing its credential
+ if webauthnValue == nil || webauthnValue.Session != nil || webauthnValue.Credential == nil {
+ removedCredentials = append(removedCredentials, cred.ID)
+ continue
+ }
+
+ webauthnValueMap, err := utils.JSONConvert[map[string]interface{}, webauthnCreds](*webauthnValue)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionParse, "webauthn credential value", &logutils.FieldArgs{"id": cred.ID}, err)
+ }
+ if webauthnValueMap == nil {
+ removedCredentials = append(removedCredentials, cred.ID)
+ continue
+ }
+
+ migrated = credential{ID: cred.ID, AuthTypeID: cred.AuthTypeID, AccountsAuthTypes: cred.AccountsAuthTypes, Value: *webauthnValueMap,
+ DateCreated: cred.DateCreated, DateUpdated: cred.DateUpdated}
+ }
+
+ if migrated.ID != "" {
+ migratedCredentials = append(migratedCredentials, migrated)
+ } else {
+ removedCredentials = append(removedCredentials, cred.ID)
+ }
+ }
+
+ _, err = sa.db.credentials.DeleteManyWithContext(context, bson.M{}, nil)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionDelete, model.TypeCredential, nil, err)
+ }
+
+ _, err = sa.db.credentials.InsertManyWithContext(context, migratedCredentials, nil)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionInsert, model.TypeCredential, nil, err)
+ }
+
+ return removedCredentials, nil
+}
+
+func (sa *Adapter) migrateAppOrgs(context TransactionContext, removedAuthTypes map[string]model.AuthType, removedCredentials []string) error {
+ appOrgs, err := sa.FindApplicationsOrganizations()
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionFind, model.TypeApplicationOrganization, nil, err)
+ }
+
+ for _, appOrg := range appOrgs {
+ updated := false
+ for i, appType := range appOrg.SupportedAuthTypes {
+ updatedIDs := make([]string, 0)
+ for j, authType := range appType.SupportedAuthTypes {
+ if newAuthType, exists := removedAuthTypes[authType.AuthTypeID]; exists {
+ if !utils.Contains(updatedIDs, newAuthType.ID) {
+ appType.SupportedAuthTypes[j] = model.SupportedAuthType{AuthTypeID: newAuthType.ID}
+ updatedIDs = append(updatedIDs, newAuthType.ID)
+ } else {
+ // remove the obsolete supported auth type if the newID is already included in the list
+ appType.SupportedAuthTypes = append(appType.SupportedAuthTypes[:j], appType.SupportedAuthTypes[j+1:]...)
+ }
+ }
+ }
+
+ if len(updatedIDs) > 0 {
+ appOrg.SupportedAuthTypes[i] = appType
+ updated = true
+ }
+ }
+
+ if updated {
+ err = sa.UpdateApplicationOrganization(context, appOrg)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeApplicationOrganization, &logutils.FieldArgs{"id": appOrg.ID}, err)
+ }
+ }
+
+ err = sa.migrateAccounts(context, appOrg, removedAuthTypes, removedCredentials)
+ if err != nil {
+ return errors.WrapErrorAction("migrating", model.TypeAccount, &logutils.FieldArgs{"app_org_id": appOrg.ID}, err)
+ }
+ }
+
+ return nil
+}
+
+func (sa *Adapter) migrateAccounts(context TransactionContext, appOrg model.ApplicationOrganization, removedAuthTypes map[string]model.AuthType, removedCredentials []string) error {
+ filter := bson.M{"app_org_id": appOrg.ID}
+ var accounts []account
+
+ err := sa.db.accounts.Find(filter, &accounts, nil)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionFind, model.TypeAccount, &logutils.FieldArgs{"app_org_id": appOrg.ID}, err)
+ }
+ if len(accounts) == 0 {
+ return nil
+ }
+
+ migratedAccounts := make([]interface{}, len(accounts))
+ for i, acct := range accounts {
+ migrated := acct
+ identifiers := make([]accountIdentifier, 0)
+ authTypes := make([]accountAuthType, 0)
+ addedIdentifiers := make([]string, 0)
+ for _, aat := range acct.AuthTypes {
+ newAat := aat
+ isExternal := (aat.Params["user"] != nil)
+ newAuthType, exists := removedAuthTypes[aat.AuthTypeID]
+ if aat.Identifier != nil && !isExternal {
+ identifier := *aat.Identifier
+ identifierCode, _ := strings.CutPrefix(aat.AuthTypeCode, "twilio_")
+ if !exists {
+ if strings.Contains(identifier, "@") {
+ identifierCode = "email"
+ } else if strings.Contains(identifier, "+") {
+ identifierCode = "phone"
+ } else {
+ identifierCode = "username"
+ }
+
+ if strings.Contains(identifier, "-") {
+ identifierParts := strings.Split(identifier, "-")
+ identifier = identifierParts[0]
+ }
+ }
+
+ if !utils.Contains(addedIdentifiers, identifier) {
+ verified := true
+ if aat.Unverified != nil {
+ verified = !*aat.Unverified
+ }
+ linked := false
+ if aat.Linked != nil {
+ linked = *aat.Linked
+ }
+
+ newIdentifier := accountIdentifier{ID: uuid.NewString(), Code: identifierCode, Identifier: identifier, Verified: verified, Linked: linked,
+ Sensitive: identifierCode == "email" || identifierCode == "phone", DateCreated: aat.DateCreated, DateUpdated: aat.DateUpdated}
+ identifiers = append(identifiers, newIdentifier)
+ addedIdentifiers = append(addedIdentifiers, identifier)
+ }
+ }
+
+ if exists {
+ // update the auth type ID and code if the current auth type was removed
+ newAat.AuthTypeID = newAuthType.ID
+ newAat.AuthTypeCode = newAuthType.Code
+ } else if isExternal {
+ // parse the external user from params
+ externalUser, err := utils.JSONConvert[model.ExternalSystemUser, interface{}](aat.Params["user"])
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionParse, model.TypeExternalSystemUser, &logutils.FieldArgs{"auth_types.id": aat.ID}, err)
+ }
+ if externalUser != nil {
+ externalAatID := aat.ID
+ linked := false
+ if aat.Linked != nil {
+ linked = *aat.Linked
+ }
+ var dateUpdated *time.Time
+ if aat.DateUpdated != nil {
+ dateUpdatedVal := *aat.DateUpdated
+ dateUpdated = &dateUpdatedVal
+ }
+
+ // add the primary external identifier
+ code := ""
+ primary := true
+ for k, v := range appOrg.IdentityProvidersSettings[0].ExternalIDFields {
+ if v == appOrg.IdentityProvidersSettings[0].UserIdentifierField {
+ code = k
+ break
+ }
+ }
+ primaryIdentifier := accountIdentifier{ID: uuid.NewString(), Code: code, Identifier: externalUser.Identifier, Verified: true, Linked: linked,
+ AccountAuthTypeID: &externalAatID, Primary: &primary, DateCreated: aat.DateCreated, DateUpdated: dateUpdated}
+ identifiers = append(identifiers, primaryIdentifier)
+
+ // add the other external identifiers from external IDs
+ for code, id := range externalUser.ExternalIDs {
+ if code != primaryIdentifier.Code {
+ primary := false
+ newIdentifier := accountIdentifier{ID: uuid.NewString(), Code: code, Identifier: id, Verified: true, Linked: linked,
+ AccountAuthTypeID: &externalAatID, Primary: &primary, DateCreated: aat.DateCreated, DateUpdated: dateUpdated}
+ identifiers = append(identifiers, newIdentifier)
+ }
+ }
+
+ // add the external email if there is one
+ if externalUser.Email != "" && externalUser.Email != externalUser.Identifier {
+ primary := false
+ externalEmail := accountIdentifier{ID: uuid.NewString(), Code: "email", Identifier: externalUser.Email, Verified: true, Linked: linked,
+ Sensitive: true, Primary: &primary, AccountAuthTypeID: &externalAatID, DateCreated: aat.DateCreated, DateUpdated: dateUpdated}
+ identifiers = append(identifiers, externalEmail)
+ }
+ }
+ }
+
+ // do not keep the account auth type if its associated credential was removed
+ if newAat.CredentialID != nil && utils.Contains(removedCredentials, *newAat.CredentialID) {
+ continue
+ }
+
+ newAat.Identifier = nil
+ newAat.Unverified = nil
+ newAat.Linked = nil
+ authTypes = append(authTypes, newAat)
+ }
+
+ now := time.Now().UTC()
+ // add profile email to identifiers if not already there
+ if acct.Profile.Email != nil && *acct.Profile.Email != "" {
+ foundEmail := false
+ for _, identifier := range identifiers {
+ if identifier.Code == "email" && identifier.Identifier == *acct.Profile.Email {
+ foundEmail = true
+ break
+ }
+ }
+ if !foundEmail {
+ emailIdentifier := accountIdentifier{ID: uuid.NewString(), Code: "email", Identifier: *acct.Profile.Email, Sensitive: true, DateCreated: now}
+ identifiers = append(identifiers, emailIdentifier)
+ }
+ }
+ // add profile phone to identifiers if not already there
+ if acct.Profile.Phone != nil && *acct.Profile.Phone != "" {
+ foundPhone := false
+ for _, identifier := range identifiers {
+ if identifier.Code == "phone" && identifier.Identifier == *acct.Profile.Phone {
+ foundPhone = true
+ break
+ }
+ }
+ if !foundPhone {
+ identifiers = append(identifiers, accountIdentifier{ID: uuid.NewString(), Code: "phone", Identifier: *acct.Profile.Phone, Sensitive: true, DateCreated: now})
+ }
+ }
+ // add account username to identifiers if not already there
+ if acct.Username != nil && *acct.Username != "" {
+ foundUsername := false
+ for _, identifier := range identifiers {
+ if identifier.Code == "username" {
+ foundUsername = true
+ break
+ }
+ }
+ if !foundUsername {
+ identifiers = append(identifiers, accountIdentifier{ID: uuid.NewString(), Code: "username", Identifier: *acct.Username, Verified: true, DateCreated: now})
+ }
+ }
+
+ migrated.AuthTypes = authTypes
+ migrated.Identifiers = identifiers
+ migrated.ExternalIDs = nil
+ migrated.Profile.Email = nil
+ migrated.Profile.Phone = nil
+ migrated.Username = nil
+ migratedAccounts[i] = migrated
+ }
+
+ _, err = sa.db.accounts.DeleteManyWithContext(context, filter, nil)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionDelete, model.TypeAccount, nil, err)
+ }
+
+ _, err = sa.db.accounts.InsertManyWithContext(context, migratedAccounts, nil)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionInsert, model.TypeAccount, nil, err)
+ }
+
+ return nil
+}
+
+func (sa *Adapter) migrateLoginSessions(context TransactionContext, removedAuthTypes map[string]model.AuthType) error {
+ // remove the following fields from all login sessions
+ update := bson.D{primitive.E{Key: "$unset", Value: bson.D{
+ primitive.E{Key: "account_auth_type_id", Value: 1},
+ primitive.E{Key: "account_auth_type_identifier", Value: 1},
+ primitive.E{Key: "external_ids", Value: 1},
+ }}}
+ res, err := sa.db.loginsSessions.UpdateManyWithContext(context, bson.M{}, update, nil)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeLoginSession, nil, err)
+ }
+ if res.ModifiedCount != res.MatchedCount {
+ return errors.ErrorAction(logutils.ActionUpdate, model.TypeLoginSession, &logutils.FieldArgs{"matched": res.MatchedCount, "modified": res.ModifiedCount})
+ }
+
+ for oldCode, authType := range removedAuthTypes {
+ // update the auth_type_code field for all removed auth types
+ update := bson.D{
+ primitive.E{Key: "$set", Value: bson.D{
+ primitive.E{Key: "auth_type_code", Value: authType.Code},
+ }},
+ }
+
+ res, err := sa.db.loginsSessions.UpdateManyWithContext(context, bson.M{"auth_type_code": oldCode}, update, nil)
+ if err != nil {
+ return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeLoginSession, &logutils.FieldArgs{"auth_type_code": oldCode}, err)
+ }
+ if res.ModifiedCount != res.MatchedCount {
+ return errors.ErrorAction(logutils.ActionUpdate, model.TypeLoginSession, &logutils.FieldArgs{"auth_type_code": oldCode, "matched": res.MatchedCount, "modified": res.ModifiedCount})
+ }
+ }
+
+ return nil
+}
diff --git a/driven/storage/model_auth.go b/driven/storage/model_auth.go
index ee12f63c9..ef9b4851f 100644
--- a/driven/storage/model_auth.go
+++ b/driven/storage/model_auth.go
@@ -34,11 +34,7 @@ type loginSession struct {
Anonymous bool `bson:"anonymous"`
- Identifier string `bson:"identifier"`
- ExternalIDs map[string]string `bson:"external_ids"`
-
- AccountAuthTypeID *string `bson:"account_auth_type_id"`
- AccountAuthTypeIdentifier *string `bson:"account_auth_type_identifier"`
+ Identifier string `bson:"identifier"`
DeviceID *string `bson:"device_id"`
diff --git a/driven/storage/model_user.go b/driven/storage/model_user.go
index 05000b739..7c5e8b43c 100644
--- a/driven/storage/model_user.go
+++ b/driven/storage/model_user.go
@@ -30,12 +30,11 @@ type account struct {
Groups []accountGroup `bson:"groups,omitempty"`
Scopes []string `bson:"scopes,omitempty"`
- AuthTypes []accountAuthType `bson:"auth_types,omitempty"`
+ Identifiers []accountIdentifier `bson:"identifiers,omitempty"`
+ AuthTypes []accountAuthType `bson:"auth_types,omitempty"`
MFATypes []mfaType `bson:"mfa_types,omitempty"`
- Username string `bson:"username"`
- ExternalIDs map[string]string `bson:"external_ids"`
Preferences map[string]interface{} `bson:"preferences"`
SystemConfigs map[string]interface{} `bson:"system_configs"`
Profile profile `bson:"profile"`
@@ -54,6 +53,11 @@ type account struct {
LastLoginDate *time.Time `bson:"last_login_date"`
LastAccessTokenDate *time.Time `bson:"last_access_token_date"`
MostRecentClientVersion *string `bson:"most_recent_client_version"`
+
+ // Deprecated:
+ Username *string `bson:"username,omitempty"`
+ // Deprecated:
+ ExternalIDs map[string]string `bson:"external_ids,omitempty"`
}
type accountRole struct {
@@ -72,12 +76,35 @@ type accountAuthType struct {
ID string `bson:"id"`
AuthTypeID string `bson:"auth_type_id"`
AuthTypeCode string `bson:"auth_type_code"`
- Identifier string `bson:"identifier"`
Params map[string]interface{} `bson:"params"`
CredentialID *string `bson:"credential_id"`
Active bool `bson:"active"`
- Unverified bool `bson:"unverified"`
- Linked bool `bson:"linked"`
+
+ // Deprecated:
+ Identifier *string `bson:"identifier,omitempty"`
+ // Deprecated:
+ Unverified *bool `bson:"unverified,omitempty"`
+ // Deprecated:
+ Linked *bool `bson:"linked,omitempty"`
+
+ DateCreated time.Time `bson:"date_created"`
+ DateUpdated *time.Time `bson:"date_updated"`
+}
+
+type accountIdentifier struct {
+ ID string `bson:"id"`
+ Code string `bson:"code"`
+ Identifier string `bson:"identifier"`
+
+ Verified bool `bson:"verified"`
+ Linked bool `bson:"linked"`
+ Sensitive bool `bson:"sensitive"`
+
+ AccountAuthTypeID *string `bson:"account_auth_type_id"`
+ Primary *bool `bson:"primary,omitempty"`
+
+ VerificationCode *string `bson:"verification_code,omitempty"`
+ VerificationExpiry *time.Time `bson:"verification_expiry,omitempty"`
DateCreated time.Time `bson:"date_created"`
DateUpdated *time.Time `bson:"date_updated"`
@@ -89,8 +116,6 @@ type profile struct {
PhotoURL string `bson:"photo_url"`
FirstName string `bson:"first_name"`
LastName string `bson:"last_name"`
- Email string `bson:"email"`
- Phone string `bson:"phone"`
BirthYear int16 `bson:"birth_year"`
Address string `bson:"address"`
ZipCode string `bson:"zip_code"`
@@ -101,6 +126,11 @@ type profile struct {
DateUpdated *time.Time `bson:"date_updated"`
UnstructuredProperties map[string]interface{} `bson:"unstructured_properties"`
+
+ // Deprecated:
+ Email *string `bson:"email,omitempty"`
+ // Deprecated:
+ Phone *string `bson:"phone,omitempty"`
}
type userDevice struct {
@@ -133,7 +163,6 @@ type credential struct {
AuthTypeID string `bson:"auth_type_id"`
AccountsAuthTypes []string `bson:"account_auth_types"`
- Verified bool `bson:"verified"`
Value map[string]interface{} `bson:"value"`
DateCreated time.Time `bson:"date_created"`
diff --git a/driver/web/adapter.go b/driver/web/adapter.go
index f970bf0cf..306a03991 100644
--- a/driver/web/adapter.go
+++ b/driver/web/adapter.go
@@ -93,7 +93,9 @@ func (we Adapter) Start() {
//ui
subRouter.HandleFunc("/ui/credential/reset", we.serveResetCredential) //Public
- subRouter.HandleFunc("/ui/credential/verify", we.uiWrapFunc(we.servicesApisHandler.verifyCredential, nil)).Methods("GET") //Public (validates code)
+ subRouter.HandleFunc("/ui/webauthn-test", we.serveWebAuthnTest) //Public
+ subRouter.HandleFunc("/ui/identifier/verify", we.uiWrapFunc(we.servicesApisHandler.verifyIdentifier, nil)).Methods("GET") //Public (validates code)
+ // subRouter.HandleFunc("/ui/webauthn-test", we.serveWebAuthnTest) //Public
///default ///
subRouter.HandleFunc("/version", we.wrapFunc(we.defaultApisHandler.getVersion, nil)).Methods("GET") //Public
@@ -107,13 +109,16 @@ func (we Adapter) Start() {
servicesSubRouter.HandleFunc("/auth/login-url", we.wrapFunc(we.servicesApisHandler.loginURL, nil)).Methods("POST") //Requires API key in request
servicesSubRouter.HandleFunc("/auth/refresh", we.wrapFunc(we.servicesApisHandler.refresh, nil)).Methods("POST") //Requires API key in request
servicesSubRouter.HandleFunc("/auth/logout", we.wrapFunc(we.servicesApisHandler.logout, we.auth.services.User)).Methods("POST")
- servicesSubRouter.HandleFunc("/auth/account/exists", we.wrapFunc(we.servicesApisHandler.accountExists, nil)).Methods("POST") //Requires API key in request
- servicesSubRouter.HandleFunc("/auth/account/can-sign-in", we.wrapFunc(we.servicesApisHandler.canSignIn, nil)).Methods("POST") //Requires API key in request
- servicesSubRouter.HandleFunc("/auth/account/can-link", we.wrapFunc(we.servicesApisHandler.canLink, nil)).Methods("POST") //Requires API key in request
+ servicesSubRouter.HandleFunc("/auth/account/exists", we.wrapFunc(we.servicesApisHandler.accountExists, nil)).Methods("POST") //Requires API key in request
+ servicesSubRouter.HandleFunc("/auth/account/can-sign-in", we.wrapFunc(we.servicesApisHandler.canSignIn, nil)).Methods("POST") //Requires API key in request
+ servicesSubRouter.HandleFunc("/auth/account/can-link", we.wrapFunc(we.servicesApisHandler.canLink, nil)).Methods("POST") //Requires API key in request
+ servicesSubRouter.HandleFunc("/auth/account/sign-in-options", we.wrapFunc(we.servicesApisHandler.signInOptions, nil)).Methods("POST") //Requires API key in request
+ servicesSubRouter.HandleFunc("/auth/account/identifier/link", we.wrapFunc(we.servicesApisHandler.linkAccountIdentifier, we.auth.services.Authenticated)).Methods("POST")
+ servicesSubRouter.HandleFunc("/auth/account/identifier/link", we.wrapFunc(we.servicesApisHandler.unlinkAccountIdentifier, we.auth.services.Authenticated)).Methods("DELETE")
servicesSubRouter.HandleFunc("/auth/account/auth-type/link", we.wrapFunc(we.servicesApisHandler.linkAccountAuthType, we.auth.services.Authenticated)).Methods("POST")
servicesSubRouter.HandleFunc("/auth/account/auth-type/link", we.wrapFunc(we.servicesApisHandler.unlinkAccountAuthType, we.auth.services.Authenticated)).Methods("DELETE")
- servicesSubRouter.HandleFunc("/auth/credential/verify", we.wrapFunc(we.servicesApisHandler.verifyCredential, nil)).Methods("GET") //Public (validates code)
- servicesSubRouter.HandleFunc("/auth/credential/send-verify", we.wrapFunc(we.servicesApisHandler.sendVerifyCredential, nil)).Methods("POST") //Requires API key in request
+ servicesSubRouter.HandleFunc("/auth/identifier/verify", we.wrapFunc(we.servicesApisHandler.verifyIdentifier, nil)).Methods("GET") //Public (validates code)
+ servicesSubRouter.HandleFunc("/auth/identifier/send-verify", we.wrapFunc(we.servicesApisHandler.sendVerifyIdentifier, nil)).Methods("POST") //Requires API key in request
servicesSubRouter.HandleFunc("/auth/credential/forgot/initiate", we.wrapFunc(we.servicesApisHandler.forgotCredentialInitiate, nil)).Methods("POST") //Requires API key in request
servicesSubRouter.HandleFunc("/auth/credential/forgot/complete", we.wrapFunc(we.servicesApisHandler.forgotCredentialComplete, nil)).Methods("POST") //Public
servicesSubRouter.HandleFunc("/auth/credential/update", we.wrapFunc(we.servicesApisHandler.updateCredential, we.auth.services.Authenticated)).Methods("POST")
@@ -142,8 +147,9 @@ func (we Adapter) Start() {
servicesSubRouter.HandleFunc("/app-configs", we.wrapFunc(we.servicesApisHandler.getApplicationConfigs, nil)).Methods("POST") //Requires API key in request
servicesSubRouter.HandleFunc("/app-configs/organization", we.wrapFunc(we.servicesApisHandler.getApplicationOrgConfigs, we.auth.services.Standard)).Methods("POST")
- // DEPRECATED
- servicesSubRouter.HandleFunc("/application/configs", we.wrapFunc(we.servicesApisHandler.getApplicationConfigs, nil)).Methods("POST") //Requires API key in request
+ // Deprecated:
+ servicesSubRouter.HandleFunc("/auth/credential/send-verify", we.wrapFunc(we.servicesApisHandler.sendVerifyIdentifier, nil)).Methods("POST") //Requires API key in request
+ servicesSubRouter.HandleFunc("/application/configs", we.wrapFunc(we.servicesApisHandler.getApplicationConfigs, nil)).Methods("POST") //Requires API key in request
servicesSubRouter.HandleFunc("/application/organization/configs", we.wrapFunc(we.servicesApisHandler.getApplicationOrgConfigs, we.auth.services.Standard)).Methods("POST")
///
@@ -313,6 +319,11 @@ func (we Adapter) serveResetCredential(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./driver/web/ui/reset-credential.html")
}
+func (we Adapter) serveWebAuthnTest(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("access-control-allow-origin", "*")
+ http.ServeFile(w, r, "./driver/web/ui/webauthn-test.html")
+}
+
func (we Adapter) serveDoc(w http.ResponseWriter, r *http.Request) {
w.Header().Add("access-control-allow-origin", "*")
diff --git a/driver/web/apis_admin.go b/driver/web/apis_admin.go
index 36201448a..2078a856d 100644
--- a/driver/web/apis_admin.go
+++ b/driver/web/apis_admin.go
@@ -101,16 +101,12 @@ func (h AdminApisHandler) login(l *logs.Log, r *http.Request, claims *tokenauth.
//privacy
requestPrivacy := privacyFromDefNullable(requestData.Privacy)
- username := ""
- if requestData.Username != nil {
- username = *requestData.Username
- }
-
//device
requestDevice := requestData.Device
- message, loginSession, mfaTypes, err := h.coreAPIs.Auth.Login(ip, string(requestDevice.Type), requestDevice.Os, requestDevice.DeviceId, string(requestData.AuthType),
- requestCreds, requestData.ApiKey, requestData.AppTypeIdentifier, requestData.OrgId, requestParams, &clientVersion, requestProfile, requestPrivacy, requestPreferences, username, true, l)
+ noLoginParams, loginSession, mfaTypes, err := h.coreAPIs.Auth.Login(ip, string(requestDevice.Type), requestDevice.Os, requestDevice.DeviceId, string(requestData.AuthType),
+ requestCreds, requestData.ApiKey, requestData.AppTypeIdentifier, requestData.OrgId, requestParams, &clientVersion, requestProfile, requestPrivacy, requestPreferences,
+ requestData.AccountIdentifierId, true, l)
if err != nil {
loggingErr, ok := err.(*errors.Error)
if ok && loggingErr.Status() != "" {
@@ -121,9 +117,25 @@ func (h AdminApisHandler) login(l *logs.Log, r *http.Request, claims *tokenauth.
///prepare response
- //message
- if message != nil {
- responseData := &Def.SharedResLogin{Message: message}
+ //noLoginParams
+ if noLoginParams != nil {
+ var message *string
+ if messageVal, _ := noLoginParams["message"].(string); messageVal != "" {
+ message = &messageVal
+ }
+
+ var paramsRes Def.SharedResLogin_Params
+ paramsBytes, err := json.Marshal(noLoginParams)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.MessageDataType("no login response params"), nil, err, http.StatusInternalServerError, false)
+ }
+
+ err = json.Unmarshal(paramsBytes, ¶msRes)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.MessageDataType("no login response params"), nil, err, http.StatusInternalServerError, false)
+ }
+
+ responseData := &Def.SharedResLogin{Message: message, Params: ¶msRes}
respData, err := json.Marshal(responseData)
if err != nil {
return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.MessageDataType("auth login response"), nil, err, http.StatusInternalServerError, false)
@@ -132,7 +144,7 @@ func (h AdminApisHandler) login(l *logs.Log, r *http.Request, claims *tokenauth.
}
if loginSession.State != "" {
- paramsRes, err := convert[Def.SharedResLoginMfa_Params](loginSession.Params)
+ paramsRes, err := utils.JSONConvert[Def.SharedResLoginMfa_Params](loginSession.Params)
if err != nil {
return l.HTTPResponseErrorAction("converting", logutils.MessageDataType("auth login response params"), nil, err, http.StatusInternalServerError, false)
}
@@ -226,7 +238,7 @@ func (h AdminApisHandler) refresh(l *logs.Log, r *http.Request, claims *tokenaut
accessToken := loginSession.AccessToken
refreshToken := loginSession.CurrentRefreshToken()
- paramsRes, err := convert[Def.SharedResRefresh_Params](loginSession.Params)
+ paramsRes, err := utils.JSONConvert[Def.SharedResRefresh_Params](loginSession.Params)
if err != nil {
return l.HTTPResponseErrorAction("converting", logutils.MessageDataType("auth refresh response params"), nil, err, http.StatusInternalServerError, false)
}
@@ -897,7 +909,7 @@ func (h AdminApisHandler) getAccount(l *logs.Log, r *http.Request, claims *token
var accountData *Def.Account
if account != nil {
- account.SortAccountAuthTypes(claims.UID)
+ account.SortAccountAuthTypes("", claims.AuthType)
accountData = accountToDef(*account)
}
@@ -942,14 +954,15 @@ func (h AdminApisHandler) createAdminAccount(l *logs.Log, r *http.Request, claim
profile := profileFromDefNullable(requestData.Profile)
privacy := privacyFromDefNullable(requestData.Privacy)
- username := ""
- if requestData.Username != nil {
- username = *requestData.Username
+ //identifier
+ requestIdentifier, err := interfaceToJSON(requestData.Identifier)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionMarshal, model.TypeCreds, nil, err, http.StatusBadRequest, true)
}
creatorPermissions := strings.Split(claims.Permissions, ",")
account, params, err := h.coreAPIs.Auth.CreateAdminAccount(string(requestData.AuthType), claims.AppID, claims.OrgID,
- requestData.Identifier, profile, privacy, username, permissions, roleIDs, groupIDs, scopes, creatorPermissions, &clientVersion, l)
+ requestIdentifier, profile, privacy, permissions, roleIDs, groupIDs, scopes, creatorPermissions, &clientVersion, l)
if err != nil || account == nil {
return l.HTTPResponseErrorAction(logutils.ActionCreate, model.TypeAccount, nil, err, http.StatusInternalServerError, true)
}
@@ -992,8 +1005,15 @@ func (h AdminApisHandler) updateAdminAccount(l *logs.Log, r *http.Request, claim
if requestData.Scopes != nil {
scopes = *requestData.Scopes
}
+
+ //identifier
+ requestIdentifier, err := interfaceToJSON(requestData.Identifier)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionMarshal, model.TypeCreds, nil, err, http.StatusBadRequest, true)
+ }
+
updaterPermissions := strings.Split(claims.Permissions, ",")
- account, params, err := h.coreAPIs.Auth.UpdateAdminAccount(string(requestData.AuthType), claims.AppID, claims.OrgID, requestData.Identifier,
+ account, params, err := h.coreAPIs.Auth.UpdateAdminAccount(string(requestData.AuthType), claims.AppID, claims.OrgID, requestIdentifier,
permissions, roleIDs, groupIDs, scopes, updaterPermissions, l)
if err != nil || account == nil {
return l.HTTPResponseErrorAction(logutils.ActionUpdate, model.TypeAccount, nil, err, http.StatusInternalServerError, true)
diff --git a/driver/web/apis_services.go b/driver/web/apis_services.go
index 5442287a5..925c95b95 100644
--- a/driver/web/apis_services.go
+++ b/driver/web/apis_services.go
@@ -20,6 +20,7 @@ import (
Def "core-building-block/driver/web/docs/gen"
"core-building-block/utils"
"encoding/json"
+ "fmt"
"io/ioutil"
"net/http"
"strconv"
@@ -31,6 +32,8 @@ import (
"github.com/rokwire/logging-library-go/v2/errors"
"github.com/rokwire/logging-library-go/v2/logs"
"github.com/rokwire/logging-library-go/v2/logutils"
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
)
// ServicesApisHandler handles the rest APIs implementation
@@ -81,16 +84,12 @@ func (h ServicesApisHandler) login(l *logs.Log, r *http.Request, claims *tokenau
// privacy
requestPrivacy := privacyFromDefNullable(requestData.Privacy)
- username := ""
- if requestData.Username != nil {
- username = *requestData.Username
- }
-
//device
requestDevice := requestData.Device
- message, loginSession, mfaTypes, err := h.coreAPIs.Auth.Login(ip, string(requestDevice.Type), requestDevice.Os, requestDevice.DeviceId, string(requestData.AuthType),
- requestCreds, requestData.ApiKey, requestData.AppTypeIdentifier, requestData.OrgId, requestParams, &clientVersion, requestProfile, requestPrivacy, requestPreferences, username, false, l)
+ noLoginParams, loginSession, mfaTypes, err := h.coreAPIs.Auth.Login(ip, string(requestDevice.Type), requestDevice.Os, requestDevice.DeviceId, string(requestData.AuthType),
+ requestCreds, requestData.ApiKey, requestData.AppTypeIdentifier, requestData.OrgId, requestParams, &clientVersion, requestProfile, requestPrivacy, requestPreferences,
+ requestData.AccountIdentifierId, false, l)
if err != nil {
loggingErr, ok := err.(*errors.Error)
if ok && loggingErr.Status() != "" {
@@ -101,9 +100,25 @@ func (h ServicesApisHandler) login(l *logs.Log, r *http.Request, claims *tokenau
///prepare response
- //message
- if message != nil {
- responseData := &Def.SharedResLogin{Message: message}
+ //noLoginParams
+ if noLoginParams != nil {
+ var message *string
+ if messageVal, _ := noLoginParams["message"].(string); messageVal != "" {
+ message = &messageVal
+ }
+
+ var paramsRes Def.SharedResLogin_Params
+ paramsBytes, err := json.Marshal(noLoginParams)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.MessageDataType("no login response params"), nil, err, http.StatusInternalServerError, false)
+ }
+
+ err = json.Unmarshal(paramsBytes, ¶msRes)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.MessageDataType("no login response params"), nil, err, http.StatusInternalServerError, false)
+ }
+
+ responseData := &Def.SharedResLogin{Message: message, Params: ¶msRes}
respData, err := json.Marshal(responseData)
if err != nil {
return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.MessageDataType("auth login response"), nil, err, http.StatusInternalServerError, false)
@@ -112,7 +127,7 @@ func (h ServicesApisHandler) login(l *logs.Log, r *http.Request, claims *tokenau
}
if loginSession.State != "" {
- paramsRes, err := convert[Def.SharedResLoginMfa_Params](loginSession.Params)
+ paramsRes, err := utils.JSONConvert[Def.SharedResLoginMfa_Params](loginSession.Params)
if err != nil {
return l.HTTPResponseErrorAction("converting", logutils.MessageDataType("auth login response params"), nil, err, http.StatusInternalServerError, false)
}
@@ -180,7 +195,7 @@ func (h ServicesApisHandler) refresh(l *logs.Log, r *http.Request, claims *token
accessToken := loginSession.AccessToken
refreshToken := loginSession.CurrentRefreshToken()
- paramsRes, err := convert[Def.SharedResRefresh_Params](loginSession.Params)
+ paramsRes, err := utils.JSONConvert[Def.SharedResRefresh_Params](loginSession.Params)
if err != nil {
return l.HTTPResponseErrorAction("converting", logutils.MessageDataType("auth refresh response params"), nil, err, http.StatusInternalServerError, false)
}
@@ -234,7 +249,20 @@ func (h ServicesApisHandler) accountExists(l *logs.Log, r *http.Request, claims
return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.TypeRequest, nil, err, http.StatusBadRequest, true)
}
- accountExists, err := h.coreAPIs.Auth.AccountExists(string(requestData.AuthType), requestData.UserIdentifier, requestData.ApiKey, requestData.AppTypeIdentifier, requestData.OrgId)
+ //identifier
+ requestIdentifier, err := interfaceToJSON(requestData.Identifier)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionMarshal, model.TypeCreds, nil, err, http.StatusBadRequest, true)
+ }
+
+ //auth type
+ var authType *string
+ if requestData.AuthType != nil {
+ authTypeStr := string(*requestData.AuthType)
+ authType = &authTypeStr
+ }
+
+ accountExists, err := h.coreAPIs.Auth.AccountExists(requestIdentifier, requestData.ApiKey, requestData.AppTypeIdentifier, requestData.OrgId, authType, requestData.UserIdentifier)
if err != nil {
return l.HTTPResponseErrorAction(logutils.ActionGet, logutils.MessageDataType("account exists"), nil, err, http.StatusInternalServerError, false)
}
@@ -259,7 +287,20 @@ func (h ServicesApisHandler) canSignIn(l *logs.Log, r *http.Request, claims *tok
return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.TypeRequest, nil, err, http.StatusBadRequest, true)
}
- canSignIn, err := h.coreAPIs.Auth.CanSignIn(string(requestData.AuthType), requestData.UserIdentifier, requestData.ApiKey, requestData.AppTypeIdentifier, requestData.OrgId)
+ //identifier
+ requestIdentifier, err := interfaceToJSON(requestData.Identifier)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionMarshal, model.TypeCreds, nil, err, http.StatusBadRequest, true)
+ }
+
+ //auth type
+ var authType *string
+ if requestData.AuthType != nil {
+ authTypeStr := string(*requestData.AuthType)
+ authType = &authTypeStr
+ }
+
+ canSignIn, err := h.coreAPIs.Auth.CanSignIn(requestIdentifier, requestData.ApiKey, requestData.AppTypeIdentifier, requestData.OrgId, authType, requestData.UserIdentifier)
if err != nil {
return l.HTTPResponseErrorAction(logutils.ActionGet, logutils.MessageDataType("can sign in"), nil, err, http.StatusInternalServerError, false)
}
@@ -284,7 +325,20 @@ func (h ServicesApisHandler) canLink(l *logs.Log, r *http.Request, claims *token
return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.TypeRequest, nil, err, http.StatusBadRequest, true)
}
- canLink, err := h.coreAPIs.Auth.CanLink(string(requestData.AuthType), requestData.UserIdentifier, requestData.ApiKey, requestData.AppTypeIdentifier, requestData.OrgId)
+ //identifier
+ requestIdentifier, err := interfaceToJSON(requestData.Identifier)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionMarshal, model.TypeCreds, nil, err, http.StatusBadRequest, true)
+ }
+
+ //auth type
+ var authType *string
+ if requestData.AuthType != nil {
+ authTypeStr := string(*requestData.AuthType)
+ authType = &authTypeStr
+ }
+
+ canLink, err := h.coreAPIs.Auth.CanLink(requestIdentifier, requestData.ApiKey, requestData.AppTypeIdentifier, requestData.OrgId, authType, requestData.UserIdentifier)
if err != nil {
return l.HTTPResponseErrorAction(logutils.ActionGet, logutils.MessageDataType("can link"), nil, err, http.StatusInternalServerError, false)
}
@@ -297,14 +351,46 @@ func (h ServicesApisHandler) canLink(l *logs.Log, r *http.Request, claims *token
return l.HTTPResponseSuccessJSON(respData)
}
-func (h ServicesApisHandler) linkAccountAuthType(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse {
- data, err := ioutil.ReadAll(r.Body)
+func (h ServicesApisHandler) signInOptions(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse {
+ var requestData Def.SharedReqAccountCheck
+ err := json.NewDecoder(r.Body).Decode(&requestData)
if err != nil {
- return l.HTTPResponseErrorAction(logutils.ActionRead, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false)
+ return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.TypeRequest, nil, err, http.StatusBadRequest, true)
+ }
+
+ //identifier
+ requestIdentifier, err := interfaceToJSON(requestData.Identifier)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionMarshal, model.TypeCreds, nil, err, http.StatusBadRequest, true)
+ }
+
+ //auth type
+ var authType *string
+ if requestData.AuthType != nil {
+ authTypeStr := string(*requestData.AuthType)
+ authType = &authTypeStr
+ }
+
+ identifiers, authTypes, err := h.coreAPIs.Auth.SignInOptions(requestIdentifier, requestData.ApiKey, requestData.AppTypeIdentifier, requestData.OrgId, authType, requestData.UserIdentifier, l)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionGet, logutils.MessageDataType("sign-in options"), nil, err, http.StatusInternalServerError, false)
}
+ respIdentifiers := accountIdentifiersToDef(identifiers)
+ respAuthTypes := accountAuthTypesToDef(authTypes)
+ resp := Def.SharedResSignInOptions{Identifiers: respIdentifiers, AuthTypes: respAuthTypes}
+
+ respData, err := json.Marshal(resp)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResponse, nil, err, http.StatusInternalServerError, false)
+ }
+
+ return l.HTTPResponseSuccessJSON(respData)
+}
+
+func (h ServicesApisHandler) linkAccountAuthType(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse {
var requestData Def.ServicesReqAccountAuthTypeLink
- err = json.Unmarshal(data, &requestData)
+ err := json.NewDecoder(r.Body).Decode(&requestData)
if err != nil {
return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.MessageDataType("account auth type link request"), nil, err, http.StatusBadRequest, true)
}
@@ -326,13 +412,15 @@ func (h ServicesApisHandler) linkAccountAuthType(l *logs.Log, r *http.Request, c
return l.HTTPResponseError("Error linking account auth type", err, http.StatusInternalServerError, true)
}
+ identifiers := make([]Def.AccountIdentifier, 0)
authTypes := make([]Def.AccountAuthType, 0)
if account != nil {
- account.SortAccountAuthTypes(claims.UID)
- authTypes = accountAuthTypesToDef(account.AuthTypes)
+ account.SortAccountAuthTypes("", claims.AuthType)
+ identifiers = accountIdentifiersToDef(account.Identifiers)
+ authTypes = accountAuthTypesToDefLegacy(account)
}
- responseData := &Def.ServicesResAccountAuthTypeLink{AuthTypes: authTypes, Message: message}
+ responseData := &Def.ServicesResAccountAuthTypeLink{Identifiers: &identifiers, AuthTypes: authTypes, Message: message}
respData, err := json.Marshal(responseData)
if err != nil {
return l.HTTPResponseErrorAction(logutils.ActionMarshal, "link account auth type response", nil, err, http.StatusInternalServerError, false)
@@ -342,33 +430,31 @@ func (h ServicesApisHandler) linkAccountAuthType(l *logs.Log, r *http.Request, c
}
func (h ServicesApisHandler) unlinkAccountAuthType(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse {
- data, err := ioutil.ReadAll(r.Body)
- if err != nil {
- return l.HTTPResponseErrorAction(logutils.ActionRead, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false)
- }
-
var requestData Def.ServicesReqAccountAuthTypeUnlink
- err = json.Unmarshal(data, &requestData)
+ err := json.NewDecoder(r.Body).Decode(&requestData)
if err != nil {
return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.MessageDataType("account auth type unlink request"), nil, err, http.StatusBadRequest, true)
}
- if string(requestData.AuthType) == claims.AuthType && requestData.Identifier == claims.UID {
- return l.HTTPResponseError("May not unlink account auth type currently in use", nil, http.StatusBadRequest, false)
+ var authType *string
+ if requestData.AuthType != nil {
+ authTypeStr := string(*requestData.AuthType)
+ authType = &authTypeStr
}
-
- account, err := h.coreAPIs.Auth.UnlinkAccountAuthType(claims.Subject, string(requestData.AuthType), requestData.AppTypeIdentifier, requestData.Identifier, l)
+ account, err := h.coreAPIs.Auth.UnlinkAccountAuthType(claims.Subject, requestData.Id, authType, requestData.Identifier, false, l)
if err != nil {
return l.HTTPResponseError("Error unlinking account auth type", err, http.StatusInternalServerError, true)
}
+ identifiers := make([]Def.AccountIdentifier, 0)
authTypes := make([]Def.AccountAuthType, 0)
if account != nil {
- account.SortAccountAuthTypes(claims.UID)
- authTypes = accountAuthTypesToDef(account.AuthTypes)
+ account.SortAccountAuthTypes("", claims.AuthType)
+ identifiers = accountIdentifiersToDef(account.Identifiers)
+ authTypes = accountAuthTypesToDefLegacy(account)
}
- responseData := &Def.ServicesResAccountAuthTypeLink{AuthTypes: authTypes}
+ responseData := &Def.ServicesResAccountAuthTypeLink{Identifiers: &identifiers, AuthTypes: authTypes}
respData, err := json.Marshal(responseData)
if err != nil {
return l.HTTPResponseErrorAction(logutils.ActionMarshal, "unlink account auth type response", nil, err, http.StatusInternalServerError, false)
@@ -377,6 +463,64 @@ func (h ServicesApisHandler) unlinkAccountAuthType(l *logs.Log, r *http.Request,
return l.HTTPResponseSuccessJSON(respData)
}
+func (h ServicesApisHandler) linkAccountIdentifier(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse {
+ var requestData Def.ServicesReqAccountIdentifierLink
+ err := json.NewDecoder(r.Body).Decode(&requestData)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.MessageDataType("account identifier link request"), nil, err, http.StatusBadRequest, true)
+ }
+
+ //identifier
+ requestIdentifier, err := interfaceToJSON(requestData.Identifier)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionMarshal, model.TypeCreds, nil, err, http.StatusBadRequest, true)
+ }
+
+ message, account, err := h.coreAPIs.Auth.LinkAccountIdentifier(claims.Subject, requestIdentifier, false, l)
+ if err != nil {
+ return l.HTTPResponseError("Error linking account identifier", err, http.StatusInternalServerError, true)
+ }
+
+ identifiers := make([]Def.AccountIdentifier, 0)
+ if account != nil {
+ identifiers = accountIdentifiersToDef(account.Identifiers)
+ }
+
+ responseData := &Def.ServicesResAccountIdentifierLink{Identifiers: identifiers, Message: message}
+ respData, err := json.Marshal(responseData)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionMarshal, "link account identifier response", nil, err, http.StatusInternalServerError, false)
+ }
+
+ return l.HTTPResponseSuccessJSON(respData)
+}
+
+func (h ServicesApisHandler) unlinkAccountIdentifier(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse {
+ var requestData Def.ServicesReqAccountIdentifierUnlink
+ err := json.NewDecoder(r.Body).Decode(&requestData)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.MessageDataType("account identifier unlink request"), nil, err, http.StatusBadRequest, true)
+ }
+
+ account, err := h.coreAPIs.Auth.UnlinkAccountIdentifier(claims.Subject, requestData.Id, false, l)
+ if err != nil {
+ return l.HTTPResponseError("Error unlinking account identifier", err, http.StatusInternalServerError, true)
+ }
+
+ identifiers := make([]Def.AccountIdentifier, 0)
+ if account != nil {
+ identifiers = accountIdentifiersToDef(account.Identifiers)
+ }
+
+ responseData := &Def.ServicesResAccountIdentifierLink{Identifiers: identifiers}
+ respData, err := json.Marshal(responseData)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionMarshal, "unlink account identifier response", nil, err, http.StatusInternalServerError, false)
+ }
+
+ return l.HTTPResponseSuccessJSON(respData)
+}
+
func (h ServicesApisHandler) authorizeService(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse {
data, err := ioutil.ReadAll(r.Body)
if err != nil {
@@ -451,7 +595,7 @@ func (h ServicesApisHandler) getAccount(l *logs.Log, r *http.Request, claims *to
var accountData *Def.Account
if account != nil {
- account.SortAccountAuthTypes(claims.UID)
+ account.SortAccountAuthTypes("", claims.AuthType)
accountData = accountToDef(*account)
}
@@ -496,14 +640,15 @@ func (h ServicesApisHandler) createAdminAccount(l *logs.Log, r *http.Request, cl
profile := profileFromDefNullable(requestData.Profile)
privacy := privacyFromDefNullable(requestData.Privacy)
- username := ""
- if requestData.Username != nil {
- username = *requestData.Username
+ //identifier
+ requestIdentifier, err := interfaceToJSON(requestData.Identifier)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionMarshal, model.TypeCreds, nil, err, http.StatusBadRequest, true)
}
creatorPermissions := strings.Split(claims.Permissions, ",")
account, params, err := h.coreAPIs.Auth.CreateAdminAccount(string(requestData.AuthType), claims.AppID, claims.OrgID,
- requestData.Identifier, profile, privacy, username, permissions, roleIDs, groupIDs, scopes, creatorPermissions, &clientVersion, l)
+ requestIdentifier, profile, privacy, permissions, roleIDs, groupIDs, scopes, creatorPermissions, &clientVersion, l)
if err != nil || account == nil {
return l.HTTPResponseErrorAction(logutils.ActionCreate, model.TypeAccount, nil, err, http.StatusInternalServerError, true)
}
@@ -546,8 +691,15 @@ func (h ServicesApisHandler) updateAdminAccount(l *logs.Log, r *http.Request, cl
if requestData.Scopes != nil {
scopes = *requestData.Scopes
}
+
+ //identifier
+ requestIdentifier, err := interfaceToJSON(requestData.Identifier)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionMarshal, model.TypeCreds, nil, err, http.StatusBadRequest, true)
+ }
+
updaterPermissions := strings.Split(claims.Permissions, ",")
- account, params, err := h.coreAPIs.Auth.UpdateAdminAccount(string(requestData.AuthType), claims.AppID, claims.OrgID, requestData.Identifier,
+ account, params, err := h.coreAPIs.Auth.UpdateAdminAccount(string(requestData.AuthType), claims.AppID, claims.OrgID, requestIdentifier,
permissions, roleIDs, groupIDs, scopes, updaterPermissions, l)
if err != nil || account == nil {
return l.HTTPResponseErrorAction(logutils.ActionUpdate, model.TypeAccount, nil, err, http.StatusInternalServerError, true)
@@ -634,6 +786,16 @@ func (h ServicesApisHandler) getProfile(l *logs.Log, r *http.Request, claims *to
profileResp := profileToDef(profile)
+ // maintain backwards compatibility
+ if len(profile.Accounts) == 1 {
+ if emailIdentifier := profile.Accounts[0].GetAccountIdentifier("email", ""); emailIdentifier != nil {
+ profileResp.Email = &emailIdentifier.Identifier
+ }
+ if phoneIdentifier := profile.Accounts[0].GetAccountIdentifier("phone", ""); phoneIdentifier != nil {
+ profileResp.Phone = &phoneIdentifier.Identifier
+ }
+ }
+
data, err := json.Marshal(profileResp)
if err != nil {
return l.HTTPResponseErrorAction(logutils.ActionMarshal, model.TypeProfile, nil, err, http.StatusInternalServerError, false)
@@ -1006,25 +1168,6 @@ func (h ServicesApisHandler) getTest(l *logs.Log, r *http.Request, claims *token
return l.HTTPResponseSuccessMessage(res)
}
-// Handler for verify endpoint
-func (h ServicesApisHandler) verifyCredential(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse {
- id := r.URL.Query().Get("id")
- if id == "" {
- return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeQueryParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false)
- }
-
- code := r.URL.Query().Get("code")
- if code == "" {
- return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeQueryParam, logutils.StringArgs("code"), nil, http.StatusBadRequest, false)
- }
-
- if err := h.coreAPIs.Auth.VerifyCredential(id, code, l); err != nil {
- return l.HTTPResponseErrorAction(logutils.ActionValidate, "code", nil, err, http.StatusInternalServerError, false)
- }
-
- return l.HTTPResponseSuccessMessage("Code verified successfully!")
-}
-
func (h ServicesApisHandler) getApplicationConfigs(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse {
data, err := ioutil.ReadAll(r.Body)
if err != nil {
@@ -1089,18 +1232,12 @@ func (h ServicesApisHandler) getApplicationOrgConfigs(l *logs.Log, r *http.Reque
return l.HTTPResponseSuccessJSON(response)
}
-// Handler for reset password endpoint from client application
+// Handler for reset credential endpoint from client application
func (h ServicesApisHandler) updateCredential(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse {
- accountID := claims.Subject
- data, err := ioutil.ReadAll(r.Body)
- if err != nil {
- return l.HTTPResponseErrorAction(logutils.ActionRead, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false)
- }
-
var requestData Def.ServicesReqCredentialUpdate
- err = json.Unmarshal(data, &requestData)
+ err := json.NewDecoder(r.Body).Decode(&requestData)
if err != nil {
- return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.MessageDataType("auth reset password client request"), nil, err, http.StatusBadRequest, true)
+ return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.MessageDataType("auth reset credential client request"), nil, err, http.StatusBadRequest, true)
}
//params
@@ -1109,24 +1246,19 @@ func (h ServicesApisHandler) updateCredential(l *logs.Log, r *http.Request, clai
return l.HTTPResponseErrorAction(logutils.ActionMarshal, "params", nil, err, http.StatusBadRequest, true)
}
- if err := h.coreAPIs.Auth.UpdateCredential(accountID, requestData.AccountAuthTypeId, requestParams, l); err != nil {
- return l.HTTPResponseErrorAction(logutils.ActionUpdate, "password", nil, err, http.StatusInternalServerError, false)
+ if err := h.coreAPIs.Auth.UpdateCredential(claims.Subject, requestData.AccountAuthTypeId, requestParams, l); err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionUpdate, "credential", nil, err, http.StatusInternalServerError, false)
}
- return l.HTTPResponseSuccessMessage("Reset Password from client successfully")
+ return l.HTTPResponseSuccessMessage("Reset credential from client successfully")
}
-// Handler for reset password endpoint from reset link
+// Handler for reset credential endpoint from reset link
func (h ServicesApisHandler) forgotCredentialComplete(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse {
- data, err := ioutil.ReadAll(r.Body)
- if err != nil {
- return l.HTTPResponseErrorAction(logutils.ActionRead, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false)
- }
-
var requestData Def.ServicesReqCredentialForgotComplete
- err = json.Unmarshal(data, &requestData)
+ err := json.NewDecoder(r.Body).Decode(&requestData)
if err != nil {
- return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.MessageDataType("auth reset password link request"), nil, err, http.StatusBadRequest, true)
+ return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.MessageDataType("auth reset credential link request"), nil, err, http.StatusBadRequest, true)
}
//params
@@ -1136,47 +1268,91 @@ func (h ServicesApisHandler) forgotCredentialComplete(l *logs.Log, r *http.Reque
}
if err := h.coreAPIs.Auth.ResetForgotCredential(requestData.CredentialId, requestData.ResetCode, requestParams, l); err != nil {
- return l.HTTPResponseErrorAction(logutils.ActionUpdate, "password", nil, err, http.StatusInternalServerError, false)
+ return l.HTTPResponseErrorAction(logutils.ActionUpdate, "credential", nil, err, http.StatusInternalServerError, false)
}
- return l.HTTPResponseSuccessMessage("Reset Password from link successfully")
+ return l.HTTPResponseSuccessMessage("Reset credential from link successfully")
}
// Handler for forgot credential endpoint
func (h ServicesApisHandler) forgotCredentialInitiate(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse {
- data, err := ioutil.ReadAll(r.Body)
+ var requestData Def.ServicesReqCredentialForgotInitiate
+ err := json.NewDecoder(r.Body).Decode(&requestData)
if err != nil {
- return l.HTTPResponseErrorAction(logutils.ActionRead, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false)
+ return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.MessageDataType("auth reset credential request"), nil, err, http.StatusBadRequest, true)
}
- var requestData Def.ServicesReqCredentialForgotInitiate
- err = json.Unmarshal(data, &requestData)
+ var requestIdentifier interface{}
+ if identifier, err := requestData.Identifier.AsSharedReqIdentifierString(); err == nil {
+ requestIdentifier = map[string]string{string(requestData.AuthType): identifier}
+ } else if identifier, err := requestData.Identifier.AsSharedReqIdentifiers(); err == nil {
+ requestIdentifier = identifier
+ } else {
+ return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.MessageDataType("auth reset credential identifier"), nil, err, http.StatusBadRequest, true)
+ }
+
+ //identifier
+ identifierJSON, err := interfaceToJSON(requestIdentifier)
if err != nil {
- return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.MessageDataType("auth reset password request"), nil, err, http.StatusBadRequest, true)
+ return l.HTTPResponseErrorAction(logutils.ActionMarshal, model.TypeCreds, nil, err, http.StatusBadRequest, true)
}
- if err := h.coreAPIs.Auth.ForgotCredential(string(requestData.AuthType), requestData.AppTypeIdentifier,
- requestData.OrgId, requestData.ApiKey, requestData.Identifier, l); err != nil {
- return l.HTTPResponseErrorAction(logutils.ActionSend, "forgot password link", nil, err, http.StatusInternalServerError, false)
+ if err := h.coreAPIs.Auth.ForgotCredential(string(requestData.AuthType), identifierJSON, requestData.AppTypeIdentifier, requestData.OrgId, requestData.ApiKey, l); err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionSend, "forgot credential link", nil, err, http.StatusInternalServerError, false)
}
- return l.HTTPResponseSuccessMessage("Sent forgot password link successfully")
+ return l.HTTPResponseSuccessMessage("Sent forgot credential link successfully")
+}
+
+// Handler for verify endpoint
+func (h ServicesApisHandler) verifyIdentifier(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse {
+ id := r.URL.Query().Get("id")
+ if id == "" {
+ return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeQueryParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false)
+ }
+
+ code := r.URL.Query().Get("code")
+ if code == "" {
+ return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeQueryParam, logutils.StringArgs("code"), nil, http.StatusBadRequest, false)
+ }
+
+ accountIdentifier, err := h.coreAPIs.Auth.VerifyIdentifier(id, code, l)
+ if err != nil {
+ return l.HTTPResponseErrorAction(logutils.ActionValidate, model.TypeAccountIdentifier, nil, err, http.StatusInternalServerError, false)
+ }
+
+ identifierStr := "Account identifier"
+ if accountIdentifier != nil && accountIdentifier.Code != "" {
+ identifierStr = cases.Title(language.English).String(accountIdentifier.Code)
+ }
+ return l.HTTPResponseSuccessMessage(fmt.Sprintf("%s verified successfully!", identifierStr))
}
// Handler for resending verify code
-func (h ServicesApisHandler) sendVerifyCredential(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse {
- data, err := ioutil.ReadAll(r.Body)
+func (h ServicesApisHandler) sendVerifyIdentifier(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse {
+ var requestData Def.ServicesReqIdentifierSendVerify
+ err := json.NewDecoder(r.Body).Decode(&requestData)
if err != nil {
- return l.HTTPResponseErrorAction(logutils.ActionRead, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false)
+ return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.MessageDataType("auth resend verification request"), nil, err, http.StatusBadRequest, true)
}
- var requestData Def.ServicesReqCredentialSendVerify
- err = json.Unmarshal(data, &requestData)
+ var requestIdentifier interface{}
+ if identifier, err := requestData.Identifier.AsSharedReqIdentifierString(); err == nil && requestData.AuthType != nil {
+ authType := string(*requestData.AuthType)
+ requestIdentifier = map[string]string{authType: identifier}
+ } else if identifier, err := requestData.Identifier.AsSharedReqIdentifiers(); err == nil {
+ requestIdentifier = identifier
+ } else {
+ return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.MessageDataType("auth resend verification identifier"), nil, err, http.StatusBadRequest, true)
+ }
+
+ //identifier
+ identifierJSON, err := interfaceToJSON(requestIdentifier)
if err != nil {
- return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.MessageDataType("auth resend verify code request"), nil, err, http.StatusBadRequest, true)
+ return l.HTTPResponseErrorAction(logutils.ActionMarshal, model.TypeCreds, nil, err, http.StatusBadRequest, true)
}
- if err := h.coreAPIs.Auth.SendVerifyCredential(string(requestData.AuthType), requestData.AppTypeIdentifier, requestData.OrgId, requestData.ApiKey, requestData.Identifier, l); err != nil {
+ if err := h.coreAPIs.Auth.SendVerifyIdentifier(requestData.AppTypeIdentifier, requestData.OrgId, requestData.ApiKey, identifierJSON, l); err != nil {
return l.HTTPResponseErrorAction(logutils.ActionSend, "code", nil, err, http.StatusInternalServerError, false)
}
diff --git a/driver/web/conversions.go b/driver/web/conversions.go
index 654a5268c..6dbf07806 100644
--- a/driver/web/conversions.go
+++ b/driver/web/conversions.go
@@ -16,43 +16,12 @@ package web
import (
"encoding/json"
- "reflect"
"time"
"github.com/rokwire/logging-library-go/v2/errors"
"github.com/rokwire/logging-library-go/v2/logutils"
)
-func convert[T any, F any](val F) (*T, error) {
- if isNil(val) {
- return nil, nil
- }
-
- bytes, err := json.Marshal(val)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionMarshal, "value", nil, err)
- }
-
- var out T
- err = json.Unmarshal(bytes, &out)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, "value", nil, err)
- }
-
- return &out, nil
-}
-
-func isNil(i interface{}) bool {
- if i == nil {
- return true
- }
- switch reflect.TypeOf(i).Kind() {
- case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice:
- return reflect.ValueOf(i).IsNil()
- }
- return false
-}
-
func defString(pointer *string) string {
if pointer == nil {
return ""
diff --git a/driver/web/conversions_application.go b/driver/web/conversions_application.go
index d484eb091..3c21c92af 100644
--- a/driver/web/conversions_application.go
+++ b/driver/web/conversions_application.go
@@ -245,8 +245,12 @@ func supportedAuthTypeFromDef(item *Def.SupportedAuthTypes) *model.AuthTypesSupp
supportedAuthTypes := []model.SupportedAuthType{}
if item.SupportedAuthTypes != nil {
for _, authType := range *item.SupportedAuthTypes {
- if authType.AuthTypeId != nil && authType.Params != nil {
- supportedAuthTypes = append(supportedAuthTypes, model.SupportedAuthType{AuthTypeID: *authType.AuthTypeId, Params: *authType.Params})
+ var params map[string]interface{}
+ if authType.Params != nil {
+ params = *authType.Params
+ }
+ if authType.AuthTypeId != nil {
+ supportedAuthTypes = append(supportedAuthTypes, model.SupportedAuthType{AuthTypeID: *authType.AuthTypeId, Params: params})
}
}
}
@@ -339,6 +343,14 @@ func identityProviderSettingFromDef(item *Def.IdentityProviderSettings) *model.I
if item.ExternalIdFields != nil {
externalIDFields = *item.ExternalIdFields
}
+ var sensitiveExternalIDs []string
+ if item.SensitiveExternalIds != nil {
+ sensitiveExternalIDs = *item.SensitiveExternalIds
+ }
+ var isEmailVerified bool
+ if item.IsEmailVerified != nil {
+ isEmailVerified = *item.IsEmailVerified
+ }
var roles map[string]string
if item.Roles != nil {
roles = *item.Roles
@@ -357,9 +369,10 @@ func identityProviderSettingFromDef(item *Def.IdentityProviderSettings) *model.I
}
return &model.IdentityProviderSetting{IdentityProviderID: item.IdentityProviderId, UserIdentifierField: item.UserIdentifierField,
- ExternalIDFields: externalIDFields, FirstNameField: firstNameField, MiddleNameField: middleNameField,
- LastNameField: lastNameField, EmailField: emailField, RolesField: rolesField, GroupsField: groupsField,
- UserSpecificFields: userSpecificFields, Roles: roles, Groups: groups, AlwaysSyncProfile: alwaysSyncProfile, IdentityBBBaseURL: identityBBBaseURL}
+ ExternalIDFields: externalIDFields, SensitiveExternalIDs: sensitiveExternalIDs, IsEmailVerified: isEmailVerified,
+ FirstNameField: firstNameField, MiddleNameField: middleNameField, LastNameField: lastNameField, EmailField: emailField, RolesField: rolesField,
+ GroupsField: groupsField, UserSpecificFields: userSpecificFields, Roles: roles, Groups: groups, AlwaysSyncProfile: alwaysSyncProfile,
+ IdentityBBBaseURL: identityBBBaseURL}
}
func identityProviderSettingsToDef(items []model.IdentityProviderSetting) []Def.IdentityProviderSettings {
@@ -382,6 +395,8 @@ func identityProviderSettingToDef(item *model.IdentityProviderSetting) *Def.Iden
}
externalIDs := item.ExternalIDFields
+ sensitiveExternalIDs := item.SensitiveExternalIDs
+ isEmailVerified := item.IsEmailVerified
roles := item.Roles
groups := item.Groups
@@ -395,9 +410,10 @@ func identityProviderSettingToDef(item *model.IdentityProviderSetting) *Def.Iden
alwaysSyncProfile := item.AlwaysSyncProfile
identityBBBaseURL := item.IdentityBBBaseURL
return &Def.IdentityProviderSettings{IdentityProviderId: item.IdentityProviderID, UserIdentifierField: item.UserIdentifierField,
- ExternalIdFields: &externalIDs, FirstNameField: &firstNameField, MiddleNameField: &middleNameField,
- LastNameField: &lastNameField, EmailField: &emailField, RolesField: &rolesField, GroupsField: &groupsField,
- UserSpecificFields: &userSpecificFields, Roles: &roles, Groups: &groups, AlwaysSyncProfile: &alwaysSyncProfile, IdentityBbBaseUrl: &identityBBBaseURL}
+ ExternalIdFields: &externalIDs, SensitiveExternalIds: &sensitiveExternalIDs, IsEmailVerified: &isEmailVerified,
+ FirstNameField: &firstNameField, MiddleNameField: &middleNameField, LastNameField: &lastNameField, EmailField: &emailField, RolesField: &rolesField,
+ GroupsField: &groupsField, UserSpecificFields: &userSpecificFields, Roles: &roles, Groups: &groups, AlwaysSyncProfile: &alwaysSyncProfile,
+ IdentityBbBaseUrl: &identityBBBaseURL}
}
// AppOrgRole
diff --git a/driver/web/conversions_auth.go b/driver/web/conversions_auth.go
index 0f5436a6e..16a9b9c97 100644
--- a/driver/web/conversions_auth.go
+++ b/driver/web/conversions_auth.go
@@ -32,12 +32,6 @@ import (
// LoginSession
func loginSessionToDef(item model.LoginSession) Def.LoginSession {
- var accountAuthTypeID *string
- var accountAuthTypeIdentifier *string
- if item.AccountAuthType != nil {
- accountAuthTypeID = &item.AccountAuthType.ID
- accountAuthTypeIdentifier = &item.AccountAuthType.Identifier
- }
var deviceID *string
if item.Device != nil {
deviceID = &item.Device.ID
@@ -52,8 +46,7 @@ func loginSessionToDef(item model.LoginSession) Def.LoginSession {
dateRefreshed := utils.FormatTime(item.DateRefreshed)
dateUpdated := utils.FormatTime(item.DateUpdated)
dateCreated := utils.FormatTime(&item.DateCreated)
- return Def.LoginSession{Id: &item.ID, Anonymous: &item.Anonymous, AuthTypeCode: &authTypeCode, AppOrgId: &appOrgID,
- AccountAuthTypeId: accountAuthTypeID, AccountAuthTypeIdentifier: accountAuthTypeIdentifier, AppTypeId: &appTypeID,
+ return Def.LoginSession{Id: &item.ID, Anonymous: &item.Anonymous, AuthTypeCode: &authTypeCode, AppOrgId: &appOrgID, AppTypeId: &appTypeID,
AppTypeIdentifier: &appTypeIdentifier, DeviceId: deviceID, Identifier: &item.Identifier, IpAddress: &item.IPAddress,
RefreshTokensCount: &refreshTokensCount, State: &item.State, MfaAttempts: &item.MfaAttempts, StateExpires: &stateExpires,
DateRefreshed: &dateRefreshed, DateUpdated: &dateUpdated, DateCreated: &dateCreated,
diff --git a/driver/web/conversions_config.go b/driver/web/conversions_config.go
index d4476a9d0..c0438d1e8 100644
--- a/driver/web/conversions_config.go
+++ b/driver/web/conversions_config.go
@@ -75,15 +75,12 @@ func configFromDef(item Def.AdminReqCreateUpdateConfig, claims *tokenauth.Claims
orgID = authutils.AllOrgs
}
- var configData interface{}
- configBytes, err := json.Marshal(item.Data)
+ configData, err := utils.JSONConvert[interface{}, Def.AdminReqCreateUpdateConfig_Data](item.Data)
if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionMarshal, model.TypeConfig, nil, err)
+ return nil, errors.WrapErrorAction(logutils.ActionParse, model.TypeConfig, nil, err)
}
-
- err = json.Unmarshal(configBytes, &configData)
- if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, model.TypeConfig, nil, err)
+ if configData == nil {
+ return nil, errors.ErrorData(logutils.StatusInvalid, model.TypeConfigData, nil)
}
return &model.Config{Type: item.Type, AppID: appID, OrgID: orgID, System: item.System, Data: configData}, nil
}
diff --git a/driver/web/conversions_user.go b/driver/web/conversions_user.go
index f976c913d..b64c8da3b 100644
--- a/driver/web/conversions_user.go
+++ b/driver/web/conversions_user.go
@@ -37,17 +37,9 @@ func accountToDef(item model.Account) *Def.Account {
//groups
groups := accountGroupsToDef(item.GetActiveGroups())
//account auth types
- authTypes := accountAuthTypesToDef(item.AuthTypes)
- //external ids
- externalIds := map[string]interface{}{}
- for k, v := range item.ExternalIDs {
- externalIds[k] = v
- }
- //username
- var username *string
- if item.Username != "" {
- username = &item.Username
- }
+ authTypes := accountAuthTypesToDefLegacy(&item)
+ //account identifiers
+ identifiers := accountIdentifiersToDef(item.Identifiers)
//account usage information
lastLoginDate := utils.FormatTime(item.LastLoginDate)
lastAccessTokenDate := utils.FormatTime(item.LastAccessTokenDate)
@@ -57,9 +49,21 @@ func accountToDef(item model.Account) *Def.Account {
scopes = []string{}
}
+ // maintain backwards compatibility
+ var username *string
+ if usernameIdentifier := item.GetAccountIdentifier("username", ""); usernameIdentifier != nil {
+ username = &usernameIdentifier.Identifier
+ }
+ if emailIdentifier := item.GetAccountIdentifier("email", ""); emailIdentifier != nil {
+ profile.Email = &emailIdentifier.Identifier
+ }
+ if phoneIdentifier := item.GetAccountIdentifier("phone", ""); phoneIdentifier != nil {
+ profile.Phone = &phoneIdentifier.Identifier
+ }
+
return &Def.Account{Id: &item.ID, Anonymous: &item.Anonymous, System: &item.AppOrg.Organization.System, Permissions: &permissions, Roles: &roles, Groups: &groups,
- Privacy: privacy, Verified: &item.Verified, Scopes: &scopes, AuthTypes: &authTypes, Username: username, Profile: profile, Preferences: preferences, SystemConfigs: systemConfigs,
- LastLoginDate: &lastLoginDate, LastAccessTokenDate: &lastAccessTokenDate, MostRecentClientVersion: item.MostRecentClientVersion, ExternalIds: &externalIds}
+ Privacy: privacy, Verified: &item.Verified, Scopes: &scopes, Identifiers: &identifiers, AuthTypes: &authTypes, Profile: profile, Preferences: preferences,
+ SystemConfigs: systemConfigs, LastLoginDate: &lastLoginDate, LastAccessTokenDate: &lastAccessTokenDate, MostRecentClientVersion: item.MostRecentClientVersion, Username: username}
}
func accountsToDef(items []model.Account) []Def.Account {
@@ -85,8 +89,10 @@ func partialAccountToDef(item model.Account, params map[string]interface{}) *Def
//systemConfigs
systemConfigs := &item.SystemConfigs
+ //account identifiers
+ identifiers := accountIdentifiersToDef(item.Identifiers)
//account auth types
- authTypes := accountAuthTypesToDef(item.AuthTypes)
+ authTypes := accountAuthTypesToDefLegacy(&item)
for i := 0; i < len(authTypes); i++ {
authTypes[i].Params = nil
}
@@ -97,11 +103,6 @@ func partialAccountToDef(item model.Account, params map[string]interface{}) *Def
formatted := utils.FormatTime(item.DateUpdated)
dateUpdated = &formatted
}
- //username
- var username *string
- if item.Username != "" {
- username = &item.Username
- }
//params
var paramsData *map[string]interface{}
@@ -109,18 +110,18 @@ func partialAccountToDef(item model.Account, params map[string]interface{}) *Def
paramsData = ¶ms
}
- //external ids
- externalIds := map[string]interface{}{}
- for k, v := range item.ExternalIDs {
- externalIds[k] = v
- }
-
privacy := privacyToDef(&item.Privacy)
+ // maintain backwards compatibility
+ var username *string
+ if usernameIdentifier := item.GetAccountIdentifier("username", ""); usernameIdentifier != nil {
+ username = &usernameIdentifier.Identifier
+ }
+
return &Def.PartialAccount{Id: &item.ID, Anonymous: item.Anonymous, AppId: item.AppOrg.Application.ID, OrgId: item.AppOrg.Organization.ID, FirstName: item.Profile.FirstName,
- LastName: item.Profile.LastName, Username: username, System: &item.AppOrg.Organization.System, Permissions: permissions, Roles: roles, Groups: groups,
- Privacy: privacy, Verified: &item.Verified, Scopes: &scopes, SystemConfigs: systemConfigs, AuthTypes: authTypes, DateCreated: &dateCreated,
- DateUpdated: dateUpdated, Params: paramsData, ExternalIds: &externalIds}
+ LastName: item.Profile.LastName, System: &item.AppOrg.Organization.System, Permissions: permissions, Roles: roles, Groups: groups,
+ Privacy: privacy, Verified: &item.Verified, Scopes: &scopes, SystemConfigs: systemConfigs, Identifiers: identifiers, AuthTypes: authTypes,
+ DateCreated: &dateCreated, DateUpdated: dateUpdated, Params: paramsData, Username: username}
}
func partialAccountsToDef(items []model.Account) []Def.PartialAccount {
@@ -135,7 +136,8 @@ func partialAccountsToDef(items []model.Account) []Def.PartialAccount {
func accountAuthTypeToDef(item model.AccountAuthType) Def.AccountAuthType {
params := item.Params
- return Def.AccountAuthType{Id: item.ID, Code: item.AuthType.Code, Identifier: item.Identifier, Active: &item.Active, Unverified: &item.Unverified, Params: ¶ms}
+ code := item.SupportedAuthType.AuthType.Code
+ return Def.AccountAuthType{Id: item.ID, AuthTypeCode: code, Active: &item.Active, Params: ¶ms, Code: &code}
}
func accountAuthTypesToDef(items []model.AccountAuthType) []Def.AccountAuthType {
@@ -146,6 +148,60 @@ func accountAuthTypesToDef(items []model.AccountAuthType) []Def.AccountAuthType
return result
}
+func accountAuthTypesToDefLegacy(account *model.Account) []Def.AccountAuthType {
+ if account == nil {
+ return nil
+ }
+
+ aats := make([]Def.AccountAuthType, 0)
+ for _, aat := range account.AuthTypes {
+ resAat := accountAuthTypeToDef(aat)
+ addedLegacy := false
+ for _, id := range account.Identifiers {
+ // create the account auth type and set the identifier if the account has an identifier code matching an alias
+ code := id.Code
+ identifier := id.Identifier
+ legacyAat := resAat
+ if id.AccountAuthTypeID != nil && *id.AccountAuthTypeID == aat.ID && id.Primary != nil && *id.Primary {
+ // only use the primary identifierfor old account auth types
+ legacyAat.Identifier = &identifier
+
+ aats = append(aats, legacyAat)
+ addedLegacy = true
+ } else if id.AccountAuthTypeID == nil && utils.Contains(aat.SupportedAuthType.AuthType.Aliases, id.Code) {
+ if code == "phone" {
+ code = "twilio_" + code
+ }
+ legacyAat.Code = &code
+ legacyAat.Identifier = &identifier
+
+ aats = append(aats, legacyAat)
+ addedLegacy = true
+ }
+ }
+
+ if !addedLegacy {
+ aats = append(aats, resAat)
+ }
+ }
+
+ return aats
+}
+
+// AccountIdentifier
+func accountIdentifierToDef(item model.AccountIdentifier) Def.AccountIdentifier {
+ return Def.AccountIdentifier{Id: item.ID, Code: item.Code, Identifier: item.Identifier, Linked: item.Linked, Verified: item.Verified,
+ Sensitive: item.Sensitive, AccountAuthTypeId: item.AccountAuthTypeID}
+}
+
+func accountIdentifiersToDef(items []model.AccountIdentifier) []Def.AccountIdentifier {
+ result := make([]Def.AccountIdentifier, len(items))
+ for i, item := range items {
+ result[i] = accountIdentifierToDef(item)
+ }
+ return result
+}
+
// AccountRole
func accountRoleToDef(item model.AccountRole) Def.AppOrgRole {
permissions := applicationPermissionsToDef(item.Role.Permissions)
@@ -216,14 +272,6 @@ func profileFromDef(item *Def.Profile) model.Profile {
if item.LastName != nil {
lastName = *item.LastName
}
- var email string
- if item.Email != nil {
- email = *item.Email
- }
- var phone string
- if item.Phone != nil {
- phone = *item.Phone
- }
var birthYear int
if item.BirthYear != nil {
birthYear = *item.BirthYear
@@ -251,7 +299,7 @@ func profileFromDef(item *Def.Profile) model.Profile {
}
return model.Profile{PhotoURL: photoURL, FirstName: firstName, LastName: lastName,
- Email: email, Phone: phone, BirthYear: int16(birthYear), Address: address, ZipCode: zipCode,
+ BirthYear: int16(birthYear), Address: address, ZipCode: zipCode,
State: state, Country: country, UnstructuredProperties: unstructuredProperties}
}
@@ -263,8 +311,8 @@ func profileToDef(item *model.Profile) *Def.Profile {
itemVal := *item
birthYear := int(itemVal.BirthYear)
return &Def.Profile{Id: &itemVal.ID, PhotoUrl: &itemVal.PhotoURL, FirstName: &itemVal.FirstName, LastName: &itemVal.LastName,
- Email: &itemVal.Email, Phone: &itemVal.Phone, BirthYear: &birthYear, Address: &itemVal.Address, ZipCode: &itemVal.ZipCode,
- State: &itemVal.State, Country: &itemVal.Country, UnstructuredProperties: &itemVal.UnstructuredProperties}
+ BirthYear: &birthYear, Address: &itemVal.Address, ZipCode: &itemVal.ZipCode, State: &itemVal.State,
+ Country: &itemVal.Country, UnstructuredProperties: &itemVal.UnstructuredProperties}
}
func profileFromDefNullable(item *Def.ProfileNullable) model.Profile {
@@ -284,14 +332,6 @@ func profileFromDefNullable(item *Def.ProfileNullable) model.Profile {
if item.LastName != nil {
lastName = *item.LastName
}
- var email string
- if item.Email != nil {
- email = *item.Email
- }
- var phone string
- if item.Phone != nil {
- phone = *item.Phone
- }
var birthYear int
if item.BirthYear != nil {
birthYear = *item.BirthYear
@@ -319,7 +359,7 @@ func profileFromDefNullable(item *Def.ProfileNullable) model.Profile {
}
return model.Profile{PhotoURL: photoURL, FirstName: firstName, LastName: lastName,
- Email: email, Phone: phone, BirthYear: int16(birthYear), Address: address, ZipCode: zipCode,
+ BirthYear: int16(birthYear), Address: address, ZipCode: zipCode,
State: state, Country: country, UnstructuredProperties: unstructuredProperties}
}
diff --git a/driver/web/docs/gen/def.yaml b/driver/web/docs/gen/def.yaml
index e535e8aa8..4f7ae3575 100644
--- a/driver/web/docs/gen/def.yaml
+++ b/driver/web/docs/gen/def.yaml
@@ -211,6 +211,35 @@ paths:
type: mobile
device_id: '5555'
os: Android
+ webauthn-sign_up:
+ summary: WebAuthn - sign up
+ value:
+ auth_type: webauthn
+ app_type_identifier: edu.illinois.rokwire
+ org_id: 0a2eff20-e2cd-11eb-af68-60f81db5ecc0
+ api_key: 95a463e3-2ce8-450b-ba75-d8506b874738
+ params:
+ sign_up: true
+ name: test
+ display_name: John Doe
+ preferences:
+ key1: value1
+ key2: value2
+ profile:
+ address: address
+ birth_year: 1990
+ country: county
+ email: email
+ first_name: first name
+ last_name: last name
+ phone: '+000000000000'
+ photo_url: photo url
+ state: state
+ zip_code: zip code
+ device:
+ type: mobile
+ device_id: '5555'
+ os: Android
required: true
responses:
'200':
@@ -377,17 +406,17 @@ paths:
description: Unauthorized
'500':
description: Internal error
- /services/auth/credential/verify:
+ /services/auth/identifier/verify:
get:
tags:
- Services
summary: Validate verification code
description: |
- Validates verification code to verify account ownership
+ Validates verification code to verify account identifier ownership
parameters:
- name: id
in: query
- description: Credential ID
+ description: Account identifier ID
required: true
style: form
explode: false
@@ -415,7 +444,7 @@ paths:
description: Unauthorized
'500':
description: Internal error
- /services/auth/credential/send-verify:
+ /services/auth/identifier/send-verify:
post:
tags:
- Services
@@ -428,7 +457,7 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/_services_req_credential_send-verify'
+ $ref: '#/components/schemas/_services_req_identifier_send-verify'
required: true
responses:
'200':
@@ -657,6 +686,136 @@ paths:
description: Unauthorized
'500':
description: Internal error
+ /services/auth/account/sign-in-options:
+ post:
+ tags:
+ - Services
+ summary: Get account sign-in options
+ description: |
+ Get the sign-in options for the account with the provided parameters
+ requestBody:
+ description: |
+ Account information to be checked
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/_shared_req_AccountCheck'
+ required: true
+ responses:
+ '200':
+ description: Success
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/_shared_res_SignInOptions'
+ '400':
+ description: Bad request
+ '401':
+ description: Unauthorized
+ '500':
+ description: Internal error
+ /services/auth/account/identifier/link:
+ post:
+ tags:
+ - Services
+ summary: Link identifier
+ description: |
+ Link identifier to an existing account
+
+ **Auth:** Requires "authenticated" auth token
+ security:
+ - bearerAuth: []
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/_services_req_account_identifier-link'
+ examples:
+ email:
+ summary: Email
+ value:
+ identifier:
+ email: test@example.com
+ phone:
+ summary: Phone
+ value:
+ identifier:
+ phone: '+12223334444'
+ username:
+ summary: Username
+ value:
+ identifier:
+ username: username
+ required: true
+ responses:
+ '200':
+ description: Success
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/_services_res_account_identifier-link'
+ '400':
+ description: Bad request
+ '401':
+ description: Unauthorized
+ '500':
+ description: Internal error
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ enum:
+ - invalid
+ - unverified
+ - verification-expired
+ - already-exists
+ - not-found
+ - not-allowed
+ - internal-server-error
+ description: |
+ - `invalid`: Invalid identifier
+ - `unverified`: Unverified identifier
+ - `verification-expired`: Identifier verification expired. The verification is restarted
+ - `already-exists`: Auth type identifier already exists
+ - `not-found`: Account could not be found when `sign-up=false`
+ - `not-allowed`: Invalid operation
+ - `internal-server-error`: An undefined error occurred
+ message:
+ type: string
+ delete:
+ tags:
+ - Services
+ summary: Unlink identifier
+ description: |
+ Unlink identifier from an existing account
+
+ **Auth:** Requires "authenticated" auth token
+ security:
+ - bearerAuth: []
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/_services_req_account_identifier-unlink'
+ example:
+ id:
+ required: true
+ responses:
+ '200':
+ description: Success
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/_services_res_account_identifier-link'
+ '400':
+ description: Bad request
+ '401':
+ description: Unauthorized
+ '500':
+ description: Internal error
/services/auth/account/auth-type/link:
post:
tags:
@@ -674,34 +833,43 @@ paths:
schema:
$ref: '#/components/schemas/_services_req_account_auth-type-link'
examples:
- email-sign_up:
- summary: Email
+ password:
+ summary: Password
value:
- auth_type: email
+ auth_type: password
app_type_identifier: edu.illinois.rokwire
- org_id: 0a2eff20-e2cd-11eb-af68-60f81db5ecc0
- api_key: 95a463e3-2ce8-450b-ba75-d8506b874738
creds:
- email: test@example.com
password: test12345
params:
confirm_password: test12345
- phone:
- summary: Phone
+ code:
+ summary: Code
value:
- auth_type: twilio_phone
+ auth_type: code
app_type_identifier: edu.illinois.rokwire
- org_id: 0a2eff20-e2cd-11eb-af68-60f81db5ecc0
- api_key: 95a463e3-2ce8-450b-ba75-d8506b874738
creds:
phone: '+12223334444'
+ webauthn-begin_registration:
+ summary: Webauthn begin registration
+ value:
+ auth_type: webauthn
+ app_type_identifier: edu.illinois.rokwire
+ params:
+ display_name: Name
+ webauthn-complete_registration:
+ summary: Webauthn complete registration
+ value:
+ auth_type: webauthn
+ app_type_identifier: edu.illinois.rokwire
+ creds:
+ response:
+ params:
+ display_name: Name
illinois_oidc:
summary: Illinois OIDC
value:
auth_type: illinois_oidc
app_type_identifier: edu.illinois.rokwire
- org_id: 0a2eff20-e2cd-11eb-af68-60f81db5ecc0
- api_key: 95a463e3-2ce8-450b-ba75-d8506b874738
creds: 'https://redirect.example.com?code=ai324uith8gSEefesEguorgwsf43'
params:
redirect_uri: 'https://redirect.example.com'
@@ -733,13 +901,15 @@ paths:
- verification-expired
- already-exists
- not-found
+ - not-allowed
- internal-server-error
description: |
- `invalid`: Invalid credentials
- `unverified`: Unverified credentials
- - `verification-expired`: Credentials verification expired. The verification is restarted
- - `already-exists`: Auth type identifier already exists
+ - `verification-expired`: Identifier verification expired. The verification is restarted
+ - `already-exists`: Auth type already exists
- `not-found`: Account could not be found when `sign-up=false`
+ - `not-allowed`: Invalid operation
- `internal-server-error`: An undefined error occurred
message:
type: string
@@ -758,25 +928,8 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/_services_req_account_auth-type-unlink'
- examples:
- email:
- summary: Email
- value:
- auth_type: email
- app_type_identifier: edu.illinois.rokwire
- identifier: test@example.com
- phone:
- summary: Phone
- value:
- auth_type: twilio_phone
- app_type_identifier: edu.illinois.rokwire
- identifier: '+12223334444'
- illinois_oidc:
- summary: Illinois OIDC
- value:
- auth_type: illinois_oidc
- app_type_identifier: edu.illinois.rokwire
- identifier: '123456789'
+ example:
+ id:
required: true
responses:
'200':
@@ -1628,6 +1781,36 @@ paths:
description: AppConfig not found
'500':
description: Internal error
+ /services/auth/credential/send-verify:
+ post:
+ tags:
+ - Services
+ summary: Send verification code to identifier
+ description: |
+ Sends verification code to identifier to verify account ownership
+ deprecated: true
+ requestBody:
+ description: |
+ Account information to be checked
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/_services_req_identifier_send-verify'
+ required: true
+ responses:
+ '200':
+ description: Successful operation
+ content:
+ text/plain:
+ schema:
+ type: string
+ example: Successfully sent verification code
+ '400':
+ description: Bad request
+ '401':
+ description: Unauthorized
+ '500':
+ description: Internal error
/services/application/configs:
post:
tags:
@@ -5801,7 +5984,7 @@ paths:
description: Unauthorized
'500':
description: Internal error
- /ui/credential/verify:
+ /ui/identifier/verify:
get:
tags:
- UI
@@ -5811,7 +5994,7 @@ paths:
parameters:
- name: id
in: query
- description: Credential ID
+ description: Identifier ID
required: true
style: form
explode: false
@@ -5890,6 +6073,7 @@ components:
data:
anyOf:
- $ref: '#/components/schemas/EnvConfigData'
+ - $ref: '#/components/schemas/AuthConfigData'
date_created:
readOnly: true
type: string
@@ -5910,6 +6094,18 @@ components:
items:
type: string
nullable: true
+ AuthConfigData:
+ type: object
+ properties:
+ email_should_verify:
+ type: boolean
+ nullable: true
+ email_verify_wait_time:
+ type: integer
+ nullable: true
+ email_verify_expiry:
+ type: integer
+ nullable: true
Application:
required:
- id
@@ -6136,6 +6332,13 @@ components:
additionalProperties:
type: string
nullable: true
+ sensitive_external_ids:
+ type: array
+ items:
+ type: string
+ nullable: true
+ is_email_verified:
+ type: boolean
first_name_field:
type: string
middle_name_field:
@@ -6252,10 +6455,6 @@ components:
type: string
app_type_identifier:
type: string
- account_auth_type_id:
- type: string
- account_auth_type_identifier:
- type: string
device_id:
type: string
nullable: true
@@ -6294,7 +6493,7 @@ components:
type: string
code:
type: string
- description: username or email or phone or illinois_oidc etc
+ description: passowrd or code or webauthn or illinois_oidc etc
description:
type: string
is_external:
@@ -6571,8 +6770,6 @@ components:
type: string
app_org:
$ref: '#/components/schemas/ApplicationOrganization'
- username:
- type: string
profile:
$ref: '#/components/schemas/Profile'
privacy:
@@ -6589,9 +6786,10 @@ components:
type: boolean
system:
type: boolean
- external_ids:
- type: object
- nullable: true
+ identifiers:
+ type: array
+ items:
+ $ref: '#/components/schemas/AccountIdentifier'
auth_types:
type: array
items:
@@ -6622,6 +6820,10 @@ components:
type: string
most_recent_client_version:
type: string
+ username:
+ type: string
+ nullable: true
+ deprecated: true
PublicAccount:
required:
- id
@@ -6650,6 +6852,7 @@ components:
- roles
- groups
- anonymous
+ - identifiers
- auth_types
- date_created
type: object
@@ -6667,8 +6870,6 @@ components:
type: string
system:
type: boolean
- username:
- type: string
permissions:
type: array
items:
@@ -6685,6 +6886,10 @@ components:
type: array
items:
type: string
+ identifiers:
+ type: array
+ items:
+ $ref: '#/components/schemas/AccountIdentifier'
auth_types:
type: array
items:
@@ -6707,9 +6912,10 @@ components:
date_updated:
type: string
nullable: true
- external_ids:
- type: object
+ username:
+ type: string
nullable: true
+ deprecated: true
Profile:
required:
- id
@@ -6727,9 +6933,11 @@ components:
email:
type: string
nullable: true
+ deprecated: true
phone:
type: string
nullable: true
+ deprecated: true
birth_year:
type: integer
nullable: true
@@ -6764,9 +6972,11 @@ components:
email:
type: string
nullable: true
+ deprecated: true
phone:
type: string
nullable: true
+ deprecated: true
birth_year:
type: integer
nullable: true
@@ -6807,24 +7017,50 @@ components:
AccountAuthType:
required:
- id
- - code
- - identifier
+ - auth_type_code
type: object
properties:
id:
type: string
+ auth_type_code:
+ type: string
code:
type: string
+ deprecated: true
identifier:
type: string
+ deprecated: true
params:
type: object
additionalProperties: true
nullable: true
active:
type: boolean
- unverified:
+ AccountIdentifier:
+ required:
+ - id
+ - code
+ - identifier
+ - verified
+ - linked
+ - sensitive
+ type: object
+ properties:
+ id:
+ type: string
+ code:
+ type: string
+ identifier:
+ type: string
+ verified:
+ type: boolean
+ linked:
type: boolean
+ sensitive:
+ type: boolean
+ account_auth_type_id:
+ type: string
+ nullable: true
Device:
required:
- id
@@ -6874,10 +7110,15 @@ components:
type: string
enum:
- email
+ - phone
- twilio_phone
- illinois_oidc
+ - conde_oidc
- anonymous
- username
+ - password
+ - webauthn
+ - code
app_type_identifier:
type: string
org_id:
@@ -6886,18 +7127,19 @@ components:
type: string
creds:
anyOf:
- - $ref: '#/components/schemas/_shared_req_CredsEmail'
- - $ref: '#/components/schemas/_shared_req_CredsTwilioPhone'
+ - $ref: '#/components/schemas/_shared_req_CredsAnonymous'
+ - $ref: '#/components/schemas/_shared_req_CredsCode'
- $ref: '#/components/schemas/_shared_req_CredsOIDC'
- - $ref: '#/components/schemas/_shared_req_CredsAPIKey'
- - $ref: '#/components/schemas/_shared_req_CredsUsername'
+ - $ref: '#/components/schemas/_shared_req_CredsPassword'
+ - $ref: '#/components/schemas/_shared_req_CredsWebAuthn'
+ - $ref: '#/components/schemas/_shared_req_CredsNone'
params:
type: object
anyOf:
- - $ref: '#/components/schemas/_shared_req_ParamsEmail'
- $ref: '#/components/schemas/_shared_req_ParamsOIDC'
+ - $ref: '#/components/schemas/_shared_req_ParamsPassword'
+ - $ref: '#/components/schemas/_shared_req_ParamsWebAuthn'
- $ref: '#/components/schemas/_shared_req_ParamsNone'
- - $ref: '#/components/schemas/_shared_req_ParamsUsername'
device:
$ref: '#/components/schemas/Device'
profile:
@@ -6907,7 +7149,7 @@ components:
preferences:
type: object
nullable: true
- username:
+ account_identifier_id:
type: string
nullable: true
_shared_req_Login_Mfa:
@@ -6953,6 +7195,7 @@ components:
type: string
enum:
- illinois_oidc
+ - conde_oidc
app_type_identifier:
type: string
org_id:
@@ -6996,10 +7239,10 @@ components:
auth_type:
type: string
enum:
- - email
+ - password
- illinois_oidc
identifier:
- type: string
+ $ref: '#/components/schemas/_shared_req_Identifiers'
permissions:
type: array
items:
@@ -7020,9 +7263,6 @@ components:
$ref: '#/components/schemas/ProfileNullable'
privacy:
$ref: '#/components/schemas/PrivacyNullable'
- username:
- type: string
- nullable: true
_shared_req_UpdateAccount:
required:
- auth_type
@@ -7032,10 +7272,10 @@ components:
auth_type:
type: string
enum:
- - email
+ - password
- illinois_oidc
identifier:
- type: string
+ $ref: '#/components/schemas/_shared_req_Identifiers'
permissions:
type: array
items:
@@ -7054,81 +7294,93 @@ components:
type: string
_shared_req_AccountCheck:
required:
- - auth_type
- app_type_identifier
- org_id
- api_key
- - user_identifier
type: object
properties:
+ app_type_identifier:
+ type: string
+ org_id:
+ type: string
+ api_key:
+ type: string
+ identifier:
+ $ref: '#/components/schemas/_shared_req_Identifiers'
auth_type:
type: string
enum:
- username
- email
+ - phone
+ - anonymous
- twilio_phone
- illinois_oidc
- - anonymous
- app_type_identifier:
- type: string
- org_id:
- type: string
- api_key:
- type: string
+ - conde_oidc
+ deprecated: true
user_identifier:
type: string
- _shared_req_CredsEmail:
- required:
- - email
- - password
+ deprecated: true
+ _shared_req_CredsNone:
type: object
- description: Auth login creds for auth_type="email"
- properties:
- email:
- type: string
- password:
- type: string
- _shared_req_CredsTwilioPhone:
+ description: Auth login request creds for unlisted auth_types (None)
+ nullable: true
+ _shared_req_CredsAnonymous:
type: object
- description: Auth login creds for auth_type="twilio_phone"
- required:
- - phone
+ description: Auth login creds for auth_type="anonymous"
properties:
- phone:
- type: string
- code:
+ anonymous_id:
type: string
+ _shared_req_CredsCode:
+ type: object
+ description: Auth login creds for auth_type="code"
+ allOf:
+ - $ref: '#/components/schemas/_shared_req_Identifiers'
+ - properties:
+ code:
+ type: string
_shared_req_CredsOIDC:
type: string
description: |
Auth login creds for auth_type="oidc" (or variants)
- full redirect URI received from OIDC provider
- _shared_req_CredsUsername:
- required:
- - username
- - password
+ _shared_req_CredsPassword:
+ type: object
+ description: Auth login creds for auth_type="password"
+ allOf:
+ - $ref: '#/components/schemas/_shared_req_Identifiers'
+ - required:
+ - password
+ properties:
+ password:
+ type: string
+ _shared_req_CredsWebAuthn:
+ type: object
+ description: Auth login creds for auth_type="webauthn"
+ allOf:
+ - $ref: '#/components/schemas/_shared_req_Identifiers'
+ - properties:
+ response:
+ type: string
+ _shared_req_Identifiers:
type: object
- description: Auth login creds for auth_type="username"
+ description: Allowed identifier types
properties:
username:
type: string
- password:
+ email:
type: string
- _shared_req_CredsAPIKey:
- type: object
- description: Auth login creds for auth_type="anonymous"
- properties:
- anonymous_id:
+ phone:
type: string
- _shared_req_ParamsEmail:
+ additionalProperties:
+ type: string
+ _shared_req_IdentifierString:
+ type: string
+ description: User identifier string
+ _shared_req_ParamsNone:
type: object
- description: Auth login params for auth_type="email"
- properties:
- confirm_password:
- type: string
- description: This should match the `creds` password field when sign_up=true. This should be verified on the client side as well to reduce invalid requests.
- sign_up:
- type: boolean
+ description: Auth login request params for unlisted auth_types (None)
+ nullable: true
_shared_req_ParamsOIDC:
type: object
description: Auth login params for auth_type="oidc" (or variants)
@@ -7137,20 +7389,16 @@ components:
type: string
pkce_verifier:
type: string
- _shared_req_ParamsUsername:
+ _shared_req_ParamsPassword:
type: object
- description: Auth login params for auth_type="username"
+ description: Auth login params for auth_type="email"
properties:
confirm_password:
type: string
description: This should match the `creds` password field when sign_up=true. This should be verified on the client side as well to reduce invalid requests.
sign_up:
type: boolean
- _shared_req_ParamsNone:
- type: object
- description: Auth login request params for unlisted auth_types (None)
- nullable: true
- _shared_req_ParamsSetEmailCredential:
+ _shared_req_ParamsResetPassword:
required:
- new_password
- confirm_password
@@ -7160,6 +7408,15 @@ components:
type: string
confirm_password:
type: string
+ _shared_req_ParamsWebAuthn:
+ type: object
+ description: Auth login params for auth_type="webauthn"
+ properties:
+ display_name:
+ type: string
+ description: User's account name for display purposes
+ sign_up:
+ type: boolean
_shared_req_app-configs:
required:
- app_type_identifier
@@ -7197,7 +7454,7 @@ components:
nullable: true
anyOf:
- $ref: '#/components/schemas/_shared_res_ParamsOIDC'
- - $ref: '#/components/schemas/_shared_res_ParamsAPIKey'
+ - $ref: '#/components/schemas/_shared_res_ParamsAnonymous'
- $ref: '#/components/schemas/_shared_res_ParamsNone'
message:
type: string
@@ -7224,7 +7481,7 @@ components:
nullable: true
anyOf:
- $ref: '#/components/schemas/_shared_res_ParamsOIDC'
- - $ref: '#/components/schemas/_shared_res_ParamsAPIKey'
+ - $ref: '#/components/schemas/_shared_res_ParamsAnonymous'
- $ref: '#/components/schemas/_shared_res_ParamsNone'
_shared_res_LoginUrl:
required:
@@ -7246,7 +7503,7 @@ components:
nullable: true
anyOf:
- $ref: '#/components/schemas/_shared_res_ParamsOIDC'
- - $ref: '#/components/schemas/_shared_res_ParamsAPIKey'
+ - $ref: '#/components/schemas/_shared_res_ParamsAnonymous'
- $ref: '#/components/schemas/_shared_res_ParamsNone'
_shared_res_Mfa:
type: object
@@ -7259,12 +7516,16 @@ components:
type: object
_shared_res_AccountCheck:
type: boolean
- _shared_res_ParamsAPIKey:
+ _shared_res_ParamsAnonymous:
type: object
description: Auth login response params for auth_type="anonymous"
properties:
anonymous_id:
type: string
+ _shared_res_ParamsNone:
+ type: object
+ description: Auth login response params for unlisted auth_types (None)
+ nullable: true
_shared_res_ParamsOIDC:
type: object
description: Auth login response params for auth_type="oidc" (or variants)
@@ -7282,10 +7543,6 @@ components:
type: string
redirect_uri:
type: string
- _shared_res_ParamsNone:
- type: object
- description: Auth login response params for unlisted auth_types (None)
- nullable: true
_shared_res_RokwireToken:
type: object
properties:
@@ -7300,49 +7557,71 @@ components:
type: string
enum:
- Bearer
+ _shared_res_SignInOptions:
+ required:
+ - identifiers
+ - auth_types
+ type: object
+ properties:
+ identifiers:
+ type: array
+ items:
+ $ref: '#/components/schemas/AccountIdentifier'
+ auth_types:
+ type: array
+ items:
+ $ref: '#/components/schemas/AccountAuthType'
_services_req_account_auth-type-link:
required:
- auth_type
- app_type_identifier
- - creds
type: object
properties:
auth_type:
type: string
enum:
+ - password
+ - webauthn
+ - code
+ - illinois_oidc
+ - conde_oidc
- email
+ - phone
- twilio_phone
- - illinois_oidc
- username
app_type_identifier:
type: string
creds:
anyOf:
- - $ref: '#/components/schemas/_shared_req_CredsEmail'
- - $ref: '#/components/schemas/_shared_req_CredsTwilioPhone'
+ - $ref: '#/components/schemas/_shared_req_CredsCode'
- $ref: '#/components/schemas/_shared_req_CredsOIDC'
+ - $ref: '#/components/schemas/_shared_req_CredsPassword'
+ - $ref: '#/components/schemas/_shared_req_CredsWebAuthn'
+ - $ref: '#/components/schemas/_shared_req_CredsNone'
params:
type: object
anyOf:
- - $ref: '#/components/schemas/_shared_req_ParamsEmail'
- $ref: '#/components/schemas/_shared_req_ParamsOIDC'
+ - $ref: '#/components/schemas/_shared_req_ParamsPassword'
+ - $ref: '#/components/schemas/_shared_req_ParamsWebAuthn'
- $ref: '#/components/schemas/_shared_req_ParamsNone'
_services_req_account_auth-type-unlink:
- required:
- - auth_type
- - app_type_identifier
- - identifier
type: object
properties:
+ id:
+ type: string
auth_type:
type: string
enum:
+ - password
+ - webauthn
+ - code
+ - illinois_oidc
+ - conde_oidc
- email
+ - phone
- twilio_phone
- - illinois_oidc
- username
- app_type_identifier:
- type: string
identifier:
type: string
_services_res_account_auth-type-link:
@@ -7353,24 +7632,42 @@ components:
message:
type: string
nullable: true
+ identifiers:
+ type: array
+ items:
+ $ref: '#/components/schemas/AccountIdentifier'
auth_types:
type: array
items:
$ref: '#/components/schemas/AccountAuthType'
- _services_req_credential_update:
+ _services_req_account_identifier-link:
required:
- - account_auth_type_id
+ - identifier
type: object
properties:
- account_auth_type_id:
+ identifier:
+ $ref: '#/components/schemas/_shared_req_Identifiers'
+ _services_req_account_identifier-unlink:
+ required:
+ - id
+ type: object
+ properties:
+ id:
type: string
- params:
- type: object
- anyOf:
- - $ref: '#/components/schemas/_shared_req_ParamsSetEmailCredential'
- _services_req_credential_send-verify:
+ _services_res_account_identifier-link:
+ required:
+ - identifiers
+ type: object
+ properties:
+ message:
+ type: string
+ nullable: true
+ identifiers:
+ type: array
+ items:
+ $ref: '#/components/schemas/AccountIdentifier'
+ _services_req_identifier_send-verify:
required:
- - auth_type
- app_type_identifier
- org_id
- api_key
@@ -7378,7 +7675,9 @@ components:
type: object
properties:
identifier:
- type: string
+ oneOf:
+ - $ref: '#/components/schemas/_shared_req_Identifiers'
+ - $ref: '#/components/schemas/_shared_req_IdentifierString'
org_id:
type: string
api_key:
@@ -7389,27 +7688,41 @@ components:
type: string
enum:
- email
+ _services_req_credential_update:
+ required:
+ - account_auth_type_id
+ type: object
+ properties:
+ account_auth_type_id:
+ type: string
+ params:
+ type: object
+ anyOf:
+ - $ref: '#/components/schemas/_shared_req_ParamsResetPassword'
_services_req_credential_forgot_initiate:
required:
- auth_type
+ - identifier
- app_type_identifier
- org_id
- api_key
- - identifier
type: object
properties:
auth_type:
type: string
enum:
+ - password
- email
+ identifier:
+ oneOf:
+ - $ref: '#/components/schemas/_shared_req_Identifiers'
+ - $ref: '#/components/schemas/_shared_req_IdentifierString'
app_type_identifier:
type: string
org_id:
type: string
api_key:
type: string
- identifier:
- type: string
_services_req_credential_forgot_complete:
required:
- credential_id
@@ -7423,7 +7736,7 @@ components:
params:
type: object
anyOf:
- - $ref: '#/components/schemas/_shared_req_ParamsSetEmailCredential'
+ - $ref: '#/components/schemas/_shared_req_ParamsResetPassword'
_services_req_authorize-service:
required:
- service_id
@@ -7657,6 +7970,7 @@ components:
data:
anyOf:
- $ref: '#/components/schemas/EnvConfigData'
+ - $ref: '#/components/schemas/AuthConfigData'
_system_req_update_service-account:
type: object
properties:
diff --git a/driver/web/docs/gen/gen_types.go b/driver/web/docs/gen/gen_types.go
index e24af0ba0..d085da503 100644
--- a/driver/web/docs/gen/gen_types.go
+++ b/driver/web/docs/gen/gen_types.go
@@ -5,6 +5,7 @@ package Def
import (
"encoding/json"
+ "fmt"
"github.com/oapi-codegen/runtime"
)
@@ -64,28 +65,39 @@ const (
// Defines values for ServicesReqAccountAuthTypeLinkAuthType.
const (
+ ServicesReqAccountAuthTypeLinkAuthTypeCode ServicesReqAccountAuthTypeLinkAuthType = "code"
+ ServicesReqAccountAuthTypeLinkAuthTypeCondeOidc ServicesReqAccountAuthTypeLinkAuthType = "conde_oidc"
ServicesReqAccountAuthTypeLinkAuthTypeEmail ServicesReqAccountAuthTypeLinkAuthType = "email"
ServicesReqAccountAuthTypeLinkAuthTypeIllinoisOidc ServicesReqAccountAuthTypeLinkAuthType = "illinois_oidc"
+ ServicesReqAccountAuthTypeLinkAuthTypePassword ServicesReqAccountAuthTypeLinkAuthType = "password"
+ ServicesReqAccountAuthTypeLinkAuthTypePhone ServicesReqAccountAuthTypeLinkAuthType = "phone"
ServicesReqAccountAuthTypeLinkAuthTypeTwilioPhone ServicesReqAccountAuthTypeLinkAuthType = "twilio_phone"
ServicesReqAccountAuthTypeLinkAuthTypeUsername ServicesReqAccountAuthTypeLinkAuthType = "username"
+ ServicesReqAccountAuthTypeLinkAuthTypeWebauthn ServicesReqAccountAuthTypeLinkAuthType = "webauthn"
)
// Defines values for ServicesReqAccountAuthTypeUnlinkAuthType.
const (
+ ServicesReqAccountAuthTypeUnlinkAuthTypeCode ServicesReqAccountAuthTypeUnlinkAuthType = "code"
+ ServicesReqAccountAuthTypeUnlinkAuthTypeCondeOidc ServicesReqAccountAuthTypeUnlinkAuthType = "conde_oidc"
ServicesReqAccountAuthTypeUnlinkAuthTypeEmail ServicesReqAccountAuthTypeUnlinkAuthType = "email"
ServicesReqAccountAuthTypeUnlinkAuthTypeIllinoisOidc ServicesReqAccountAuthTypeUnlinkAuthType = "illinois_oidc"
+ ServicesReqAccountAuthTypeUnlinkAuthTypePassword ServicesReqAccountAuthTypeUnlinkAuthType = "password"
+ ServicesReqAccountAuthTypeUnlinkAuthTypePhone ServicesReqAccountAuthTypeUnlinkAuthType = "phone"
ServicesReqAccountAuthTypeUnlinkAuthTypeTwilioPhone ServicesReqAccountAuthTypeUnlinkAuthType = "twilio_phone"
ServicesReqAccountAuthTypeUnlinkAuthTypeUsername ServicesReqAccountAuthTypeUnlinkAuthType = "username"
+ ServicesReqAccountAuthTypeUnlinkAuthTypeWebauthn ServicesReqAccountAuthTypeUnlinkAuthType = "webauthn"
)
// Defines values for ServicesReqCredentialForgotInitiateAuthType.
const (
- ServicesReqCredentialForgotInitiateAuthTypeEmail ServicesReqCredentialForgotInitiateAuthType = "email"
+ ServicesReqCredentialForgotInitiateAuthTypeEmail ServicesReqCredentialForgotInitiateAuthType = "email"
+ ServicesReqCredentialForgotInitiateAuthTypePassword ServicesReqCredentialForgotInitiateAuthType = "password"
)
-// Defines values for ServicesReqCredentialSendVerifyAuthType.
+// Defines values for ServicesReqIdentifierSendVerifyAuthType.
const (
- ServicesReqCredentialSendVerifyAuthTypeEmail ServicesReqCredentialSendVerifyAuthType = "email"
+ ServicesReqIdentifierSendVerifyAuthTypeEmail ServicesReqIdentifierSendVerifyAuthType = "email"
)
// Defines values for ServicesReqServiceAccountsAccessTokenAuthType.
@@ -114,29 +126,37 @@ const (
// Defines values for SharedReqAccountCheckAuthType.
const (
SharedReqAccountCheckAuthTypeAnonymous SharedReqAccountCheckAuthType = "anonymous"
+ SharedReqAccountCheckAuthTypeCondeOidc SharedReqAccountCheckAuthType = "conde_oidc"
SharedReqAccountCheckAuthTypeEmail SharedReqAccountCheckAuthType = "email"
SharedReqAccountCheckAuthTypeIllinoisOidc SharedReqAccountCheckAuthType = "illinois_oidc"
+ SharedReqAccountCheckAuthTypePhone SharedReqAccountCheckAuthType = "phone"
SharedReqAccountCheckAuthTypeTwilioPhone SharedReqAccountCheckAuthType = "twilio_phone"
SharedReqAccountCheckAuthTypeUsername SharedReqAccountCheckAuthType = "username"
)
// Defines values for SharedReqCreateAccountAuthType.
const (
- SharedReqCreateAccountAuthTypeEmail SharedReqCreateAccountAuthType = "email"
SharedReqCreateAccountAuthTypeIllinoisOidc SharedReqCreateAccountAuthType = "illinois_oidc"
+ SharedReqCreateAccountAuthTypePassword SharedReqCreateAccountAuthType = "password"
)
// Defines values for SharedReqLoginAuthType.
const (
SharedReqLoginAuthTypeAnonymous SharedReqLoginAuthType = "anonymous"
+ SharedReqLoginAuthTypeCode SharedReqLoginAuthType = "code"
+ SharedReqLoginAuthTypeCondeOidc SharedReqLoginAuthType = "conde_oidc"
SharedReqLoginAuthTypeEmail SharedReqLoginAuthType = "email"
SharedReqLoginAuthTypeIllinoisOidc SharedReqLoginAuthType = "illinois_oidc"
+ SharedReqLoginAuthTypePassword SharedReqLoginAuthType = "password"
+ SharedReqLoginAuthTypePhone SharedReqLoginAuthType = "phone"
SharedReqLoginAuthTypeTwilioPhone SharedReqLoginAuthType = "twilio_phone"
SharedReqLoginAuthTypeUsername SharedReqLoginAuthType = "username"
+ SharedReqLoginAuthTypeWebauthn SharedReqLoginAuthType = "webauthn"
)
// Defines values for SharedReqLoginUrlAuthType.
const (
+ SharedReqLoginUrlAuthTypeCondeOidc SharedReqLoginUrlAuthType = "conde_oidc"
SharedReqLoginUrlAuthTypeIllinoisOidc SharedReqLoginUrlAuthType = "illinois_oidc"
)
@@ -157,8 +177,8 @@ const (
// Defines values for SharedReqUpdateAccountAuthType.
const (
- Email SharedReqUpdateAccountAuthType = "email"
IllinoisOidc SharedReqUpdateAccountAuthType = "illinois_oidc"
+ Password SharedReqUpdateAccountAuthType = "password"
)
// Defines values for SharedResRokwireTokenTokenType.
@@ -179,9 +199,9 @@ type Account struct {
AppOrg *ApplicationOrganization `json:"app_org"`
AuthTypes *[]AccountAuthType `json:"auth_types,omitempty"`
Devices *[]Device `json:"devices,omitempty"`
- ExternalIds *map[string]interface{} `json:"external_ids"`
Groups *[]AppOrgGroup `json:"groups,omitempty"`
Id *string `json:"id,omitempty"`
+ Identifiers *[]AccountIdentifier `json:"identifiers,omitempty"`
LastAccessTokenDate *string `json:"last_access_token_date,omitempty"`
LastLoginDate *string `json:"last_login_date,omitempty"`
MostRecentClientVersion *string `json:"most_recent_client_version,omitempty"`
@@ -193,18 +213,32 @@ type Account struct {
Scopes *[]string `json:"scopes,omitempty"`
System *bool `json:"system,omitempty"`
SystemConfigs *map[string]interface{} `json:"system_configs"`
- Username *string `json:"username,omitempty"`
- Verified *bool `json:"verified,omitempty"`
+ // Deprecated:
+ Username *string `json:"username"`
+ Verified *bool `json:"verified,omitempty"`
}
// AccountAuthType defines model for AccountAuthType.
type AccountAuthType struct {
- Active *bool `json:"active,omitempty"`
- Code string `json:"code"`
- Id string `json:"id"`
- Identifier string `json:"identifier"`
+ Active *bool `json:"active,omitempty"`
+ AuthTypeCode string `json:"auth_type_code"`
+ // Deprecated:
+ Code *string `json:"code,omitempty"`
+ Id string `json:"id"`
+ // Deprecated:
+ Identifier *string `json:"identifier,omitempty"`
Params *map[string]interface{} `json:"params"`
- Unverified *bool `json:"unverified,omitempty"`
+}
+
+// AccountIdentifier defines model for AccountIdentifier.
+type AccountIdentifier struct {
+ AccountAuthTypeId *string `json:"account_auth_type_id"`
+ Code string `json:"code"`
+ Id string `json:"id"`
+ Identifier string `json:"identifier"`
+ Linked bool `json:"linked"`
+ Sensitive bool `json:"sensitive"`
+ Verified bool `json:"verified"`
}
// AdminToken defines model for AdminToken.
@@ -284,6 +318,13 @@ type ApplicationType struct {
Versions *[]string `json:"versions,omitempty"`
}
+// AuthConfigData defines model for AuthConfigData.
+type AuthConfigData struct {
+ EmailShouldVerify *bool `json:"email_should_verify"`
+ EmailVerifyExpiry *int `json:"email_verify_expiry"`
+ EmailVerifyWaitTime *int `json:"email_verify_wait_time"`
+}
+
// AuthServiceReg Service registration record used for auth
type AuthServiceReg struct {
Host string `json:"host"`
@@ -294,7 +335,7 @@ type AuthServiceReg struct {
// AuthType defines model for AuthType.
type AuthType struct {
- // Code username or email or phone or illinois_oidc etc
+ // Code passowrd or code or webauthn or illinois_oidc etc
Code string `json:"code"`
Description string `json:"description"`
Id *string `json:"id,omitempty"`
@@ -356,20 +397,22 @@ type Follow struct {
// IdentityProviderSettings defines model for IdentityProviderSettings.
type IdentityProviderSettings struct {
- AlwaysSyncProfile *bool `json:"always_sync_profile,omitempty"`
- EmailField *string `json:"email_field,omitempty"`
- ExternalIdFields *map[string]string `json:"external_id_fields"`
- FirstNameField *string `json:"first_name_field,omitempty"`
- Groups *map[string]string `json:"groups"`
- GroupsField *string `json:"groups_field,omitempty"`
- IdentityBbBaseUrl *string `json:"identity_bb_base_url,omitempty"`
- IdentityProviderId string `json:"identity_provider_id"`
- LastNameField *string `json:"last_name_field,omitempty"`
- MiddleNameField *string `json:"middle_name_field,omitempty"`
- Roles *map[string]string `json:"roles"`
- RolesField *string `json:"roles_field,omitempty"`
- UserIdentifierField string `json:"user_identifier_field"`
- UserSpecificFields *[]string `json:"user_specific_fields"`
+ AlwaysSyncProfile *bool `json:"always_sync_profile,omitempty"`
+ EmailField *string `json:"email_field,omitempty"`
+ ExternalIdFields *map[string]string `json:"external_id_fields"`
+ FirstNameField *string `json:"first_name_field,omitempty"`
+ Groups *map[string]string `json:"groups"`
+ GroupsField *string `json:"groups_field,omitempty"`
+ IdentityBbBaseUrl *string `json:"identity_bb_base_url,omitempty"`
+ IdentityProviderId string `json:"identity_provider_id"`
+ IsEmailVerified *bool `json:"is_email_verified,omitempty"`
+ LastNameField *string `json:"last_name_field,omitempty"`
+ MiddleNameField *string `json:"middle_name_field,omitempty"`
+ Roles *map[string]string `json:"roles"`
+ RolesField *string `json:"roles_field,omitempty"`
+ SensitiveExternalIds *[]string `json:"sensitive_external_ids"`
+ UserIdentifierField string `json:"user_identifier_field"`
+ UserSpecificFields *[]string `json:"user_specific_fields"`
}
// InactiveExpirePolicy defines model for InactiveExpirePolicy.
@@ -454,24 +497,22 @@ type JWKS struct {
// LoginSession defines model for LoginSession.
type LoginSession struct {
- AccountAuthTypeId *string `json:"account_auth_type_id,omitempty"`
- AccountAuthTypeIdentifier *string `json:"account_auth_type_identifier,omitempty"`
- Anonymous *bool `json:"anonymous,omitempty"`
- AppOrgId *string `json:"app_org_id,omitempty"`
- AppTypeId *string `json:"app_type_id,omitempty"`
- AppTypeIdentifier *string `json:"app_type_identifier,omitempty"`
- AuthTypeCode *string `json:"auth_type_code,omitempty"`
- DateCreated *string `json:"date_created,omitempty"`
- DateRefreshed *string `json:"date_refreshed"`
- DateUpdated *string `json:"date_updated"`
- DeviceId *string `json:"device_id"`
- Id *string `json:"id,omitempty"`
- Identifier *string `json:"identifier,omitempty"`
- IpAddress *string `json:"ip_address,omitempty"`
- MfaAttempts *int `json:"mfa_attempts,omitempty"`
- RefreshTokensCount *int `json:"refresh_tokens_count,omitempty"`
- State *string `json:"state,omitempty"`
- StateExpires *string `json:"state_expires"`
+ Anonymous *bool `json:"anonymous,omitempty"`
+ AppOrgId *string `json:"app_org_id,omitempty"`
+ AppTypeId *string `json:"app_type_id,omitempty"`
+ AppTypeIdentifier *string `json:"app_type_identifier,omitempty"`
+ AuthTypeCode *string `json:"auth_type_code,omitempty"`
+ DateCreated *string `json:"date_created,omitempty"`
+ DateRefreshed *string `json:"date_refreshed"`
+ DateUpdated *string `json:"date_updated"`
+ DeviceId *string `json:"device_id"`
+ Id *string `json:"id,omitempty"`
+ Identifier *string `json:"identifier,omitempty"`
+ IpAddress *string `json:"ip_address,omitempty"`
+ MfaAttempts *int `json:"mfa_attempts,omitempty"`
+ RefreshTokensCount *int `json:"refresh_tokens_count,omitempty"`
+ State *string `json:"state,omitempty"`
+ StateExpires *string `json:"state_expires"`
}
// LoginSessionSettings defines model for LoginSessionSettings.
@@ -515,10 +556,10 @@ type PartialAccount struct {
AuthTypes []AccountAuthType `json:"auth_types"`
DateCreated *string `json:"date_created,omitempty"`
DateUpdated *string `json:"date_updated"`
- ExternalIds *map[string]interface{} `json:"external_ids"`
FirstName string `json:"first_name"`
Groups []AppOrgGroup `json:"groups"`
Id *string `json:"id,omitempty"`
+ Identifiers []AccountIdentifier `json:"identifiers"`
LastName string `json:"last_name"`
OrgId string `json:"org_id"`
Params *map[string]interface{} `json:"params"`
@@ -528,8 +569,9 @@ type PartialAccount struct {
Scopes *[]string `json:"scopes,omitempty"`
System *bool `json:"system,omitempty"`
SystemConfigs *map[string]interface{} `json:"system_configs"`
- Username *string `json:"username,omitempty"`
- Verified *bool `json:"verified,omitempty"`
+ // Deprecated:
+ Username *string `json:"username"`
+ Verified *bool `json:"verified,omitempty"`
}
// Permission defines model for Permission.
@@ -555,13 +597,15 @@ type PrivacyNullable struct {
// Profile defines model for Profile.
type Profile struct {
- Address *string `json:"address"`
- BirthYear *int `json:"birth_year"`
- Country *string `json:"country"`
- Email *string `json:"email"`
- FirstName *string `json:"first_name,omitempty"`
- Id *string `json:"id,omitempty"`
- LastName *string `json:"last_name,omitempty"`
+ Address *string `json:"address"`
+ BirthYear *int `json:"birth_year"`
+ Country *string `json:"country"`
+ // Deprecated:
+ Email *string `json:"email"`
+ FirstName *string `json:"first_name,omitempty"`
+ Id *string `json:"id,omitempty"`
+ LastName *string `json:"last_name,omitempty"`
+ // Deprecated:
Phone *string `json:"phone"`
PhotoUrl *string `json:"photo_url,omitempty"`
State *string `json:"state"`
@@ -571,12 +615,14 @@ type Profile struct {
// ProfileNullable defines model for ProfileNullable.
type ProfileNullable struct {
- Address *string `json:"address"`
- BirthYear *int `json:"birth_year"`
- Country *string `json:"country"`
- Email *string `json:"email"`
- FirstName *string `json:"first_name"`
- LastName *string `json:"last_name"`
+ Address *string `json:"address"`
+ BirthYear *int `json:"birth_year"`
+ Country *string `json:"country"`
+ // Deprecated:
+ Email *string `json:"email"`
+ FirstName *string `json:"first_name"`
+ LastName *string `json:"last_name"`
+ // Deprecated:
Phone *string `json:"phone"`
PhotoUrl *string `json:"photo_url"`
State *string `json:"state"`
@@ -755,7 +801,7 @@ type AdminReqVerified struct {
type ServicesReqAccountAuthTypeLink struct {
AppTypeIdentifier string `json:"app_type_identifier"`
AuthType ServicesReqAccountAuthTypeLinkAuthType `json:"auth_type"`
- Creds ServicesReqAccountAuthTypeLink_Creds `json:"creds"`
+ Creds *ServicesReqAccountAuthTypeLink_Creds `json:"creds,omitempty"`
Params *ServicesReqAccountAuthTypeLink_Params `json:"params,omitempty"`
}
@@ -774,14 +820,25 @@ type ServicesReqAccountAuthTypeLink_Params struct {
// ServicesReqAccountAuthTypeUnlink defines model for _services_req_account_auth-type-unlink.
type ServicesReqAccountAuthTypeUnlink struct {
- AppTypeIdentifier string `json:"app_type_identifier"`
- AuthType ServicesReqAccountAuthTypeUnlinkAuthType `json:"auth_type"`
- Identifier string `json:"identifier"`
+ AuthType *ServicesReqAccountAuthTypeUnlinkAuthType `json:"auth_type,omitempty"`
+ Id *string `json:"id,omitempty"`
+ Identifier *string `json:"identifier,omitempty"`
}
// ServicesReqAccountAuthTypeUnlinkAuthType defines model for ServicesReqAccountAuthTypeUnlink.AuthType.
type ServicesReqAccountAuthTypeUnlinkAuthType string
+// ServicesReqAccountIdentifierLink defines model for _services_req_account_identifier-link.
+type ServicesReqAccountIdentifierLink struct {
+ // Identifier Allowed identifier types
+ Identifier SharedReqIdentifiers `json:"identifier"`
+}
+
+// ServicesReqAccountIdentifierUnlink defines model for _services_req_account_identifier-unlink.
+type ServicesReqAccountIdentifierUnlink struct {
+ Id string `json:"id"`
+}
+
// ServicesReqAuthorizeService defines model for _services_req_authorize-service.
type ServicesReqAuthorizeService struct {
// ApprovedScopes Scopes to be granted to this service in this and future tokens. Replaces existing scopes if present.
@@ -803,28 +860,21 @@ type ServicesReqCredentialForgotComplete_Params struct {
// ServicesReqCredentialForgotInitiate defines model for _services_req_credential_forgot_initiate.
type ServicesReqCredentialForgotInitiate struct {
- ApiKey string `json:"api_key"`
- AppTypeIdentifier string `json:"app_type_identifier"`
- AuthType ServicesReqCredentialForgotInitiateAuthType `json:"auth_type"`
- Identifier string `json:"identifier"`
- OrgId string `json:"org_id"`
+ ApiKey string `json:"api_key"`
+ AppTypeIdentifier string `json:"app_type_identifier"`
+ AuthType ServicesReqCredentialForgotInitiateAuthType `json:"auth_type"`
+ Identifier ServicesReqCredentialForgotInitiate_Identifier `json:"identifier"`
+ OrgId string `json:"org_id"`
}
// ServicesReqCredentialForgotInitiateAuthType defines model for ServicesReqCredentialForgotInitiate.AuthType.
type ServicesReqCredentialForgotInitiateAuthType string
-// ServicesReqCredentialSendVerify defines model for _services_req_credential_send-verify.
-type ServicesReqCredentialSendVerify struct {
- ApiKey string `json:"api_key"`
- AppTypeIdentifier string `json:"app_type_identifier"`
- AuthType ServicesReqCredentialSendVerifyAuthType `json:"auth_type"`
- Identifier string `json:"identifier"`
- OrgId string `json:"org_id"`
+// ServicesReqCredentialForgotInitiate_Identifier defines model for ServicesReqCredentialForgotInitiate.Identifier.
+type ServicesReqCredentialForgotInitiate_Identifier struct {
+ union json.RawMessage
}
-// ServicesReqCredentialSendVerifyAuthType defines model for ServicesReqCredentialSendVerify.AuthType.
-type ServicesReqCredentialSendVerifyAuthType string
-
// ServicesReqCredentialUpdate defines model for _services_req_credential_update.
type ServicesReqCredentialUpdate struct {
AccountAuthTypeId string `json:"account_auth_type_id"`
@@ -836,6 +886,23 @@ type ServicesReqCredentialUpdate_Params struct {
union json.RawMessage
}
+// ServicesReqIdentifierSendVerify defines model for _services_req_identifier_send-verify.
+type ServicesReqIdentifierSendVerify struct {
+ ApiKey string `json:"api_key"`
+ AppTypeIdentifier string `json:"app_type_identifier"`
+ AuthType *ServicesReqIdentifierSendVerifyAuthType `json:"auth_type,omitempty"`
+ Identifier ServicesReqIdentifierSendVerify_Identifier `json:"identifier"`
+ OrgId string `json:"org_id"`
+}
+
+// ServicesReqIdentifierSendVerifyAuthType defines model for ServicesReqIdentifierSendVerify.AuthType.
+type ServicesReqIdentifierSendVerifyAuthType string
+
+// ServicesReqIdentifierSendVerify_Identifier defines model for ServicesReqIdentifierSendVerify.Identifier.
+type ServicesReqIdentifierSendVerify_Identifier struct {
+ union json.RawMessage
+}
+
// ServicesReqServiceAccountsAccessToken defines model for _services_req_service-accounts_access-token.
type ServicesReqServiceAccountsAccessToken struct {
AccountId string `json:"account_id"`
@@ -875,8 +942,15 @@ type ServicesReqServiceAccountsParamsAuthType string
// ServicesResAccountAuthTypeLink defines model for _services_res_account_auth-type-link.
type ServicesResAccountAuthTypeLink struct {
- AuthTypes []AccountAuthType `json:"auth_types"`
- Message *string `json:"message"`
+ AuthTypes []AccountAuthType `json:"auth_types"`
+ Identifiers *[]AccountIdentifier `json:"identifiers,omitempty"`
+ Message *string `json:"message"`
+}
+
+// ServicesResAccountIdentifierLink defines model for _services_res_account_identifier-link.
+type ServicesResAccountIdentifierLink struct {
+ Identifiers []AccountIdentifier `json:"identifiers"`
+ Message *string `json:"message"`
}
// ServicesResAuthorizeService defines model for _services_res_authorize-service.
@@ -908,11 +982,16 @@ type ServicesServiceAccountsCredsStaticToken struct {
// SharedReqAccountCheck defines model for _shared_req_AccountCheck.
type SharedReqAccountCheck struct {
- ApiKey string `json:"api_key"`
- AppTypeIdentifier string `json:"app_type_identifier"`
- AuthType SharedReqAccountCheckAuthType `json:"auth_type"`
- OrgId string `json:"org_id"`
- UserIdentifier string `json:"user_identifier"`
+ ApiKey string `json:"api_key"`
+ AppTypeIdentifier string `json:"app_type_identifier"`
+ // Deprecated:
+ AuthType *SharedReqAccountCheckAuthType `json:"auth_type,omitempty"`
+
+ // Identifier Allowed identifier types
+ Identifier *SharedReqIdentifiers `json:"identifier,omitempty"`
+ OrgId string `json:"org_id"`
+ // Deprecated:
+ UserIdentifier *string `json:"user_identifier,omitempty"`
}
// SharedReqAccountCheckAuthType defines model for SharedReqAccountCheck.AuthType.
@@ -920,60 +999,84 @@ type SharedReqAccountCheckAuthType string
// SharedReqCreateAccount defines model for _shared_req_CreateAccount.
type SharedReqCreateAccount struct {
- AuthType SharedReqCreateAccountAuthType `json:"auth_type"`
- GroupIds *[]string `json:"group_ids,omitempty"`
- Identifier string `json:"identifier"`
- Permissions *[]string `json:"permissions,omitempty"`
- Privacy *PrivacyNullable `json:"privacy"`
- Profile *ProfileNullable `json:"profile"`
- RoleIds *[]string `json:"role_ids,omitempty"`
- Scopes *[]string `json:"scopes,omitempty"`
- Username *string `json:"username"`
+ AuthType SharedReqCreateAccountAuthType `json:"auth_type"`
+ GroupIds *[]string `json:"group_ids,omitempty"`
+
+ // Identifier Allowed identifier types
+ Identifier SharedReqIdentifiers `json:"identifier"`
+ Permissions *[]string `json:"permissions,omitempty"`
+ Privacy *PrivacyNullable `json:"privacy"`
+ Profile *ProfileNullable `json:"profile"`
+ RoleIds *[]string `json:"role_ids,omitempty"`
+ Scopes *[]string `json:"scopes,omitempty"`
}
// SharedReqCreateAccountAuthType defines model for SharedReqCreateAccount.AuthType.
type SharedReqCreateAccountAuthType string
-// SharedReqCredsAPIKey Auth login creds for auth_type="anonymous"
-type SharedReqCredsAPIKey struct {
+// SharedReqCredsAnonymous Auth login creds for auth_type="anonymous"
+type SharedReqCredsAnonymous struct {
AnonymousId *string `json:"anonymous_id,omitempty"`
}
-// SharedReqCredsEmail Auth login creds for auth_type="email"
-type SharedReqCredsEmail struct {
- Email string `json:"email"`
- Password string `json:"password"`
+// SharedReqCredsCode defines model for _shared_req_CredsCode.
+type SharedReqCredsCode struct {
+ Code *string `json:"code,omitempty"`
+ Email *string `json:"email,omitempty"`
+ Phone *string `json:"phone,omitempty"`
+ Username *string `json:"username,omitempty"`
+ AdditionalProperties map[string]string `json:"-"`
}
+// SharedReqCredsNone Auth login request creds for unlisted auth_types (None)
+type SharedReqCredsNone = map[string]interface{}
+
// SharedReqCredsOIDC Auth login creds for auth_type="oidc" (or variants)
// - full redirect URI received from OIDC provider
type SharedReqCredsOIDC = string
-// SharedReqCredsTwilioPhone Auth login creds for auth_type="twilio_phone"
-type SharedReqCredsTwilioPhone struct {
- Code *string `json:"code,omitempty"`
- Phone string `json:"phone"`
+// SharedReqCredsPassword defines model for _shared_req_CredsPassword.
+type SharedReqCredsPassword struct {
+ Email *string `json:"email,omitempty"`
+ Password string `json:"password"`
+ Phone *string `json:"phone,omitempty"`
+ Username *string `json:"username,omitempty"`
+ AdditionalProperties map[string]string `json:"-"`
}
-// SharedReqCredsUsername Auth login creds for auth_type="username"
-type SharedReqCredsUsername struct {
- Password string `json:"password"`
- Username string `json:"username"`
+// SharedReqCredsWebAuthn defines model for _shared_req_CredsWebAuthn.
+type SharedReqCredsWebAuthn struct {
+ Email *string `json:"email,omitempty"`
+ Phone *string `json:"phone,omitempty"`
+ Response *string `json:"response,omitempty"`
+ Username *string `json:"username,omitempty"`
+ AdditionalProperties map[string]string `json:"-"`
+}
+
+// SharedReqIdentifierString User identifier string
+type SharedReqIdentifierString = string
+
+// SharedReqIdentifiers Allowed identifier types
+type SharedReqIdentifiers struct {
+ Email *string `json:"email,omitempty"`
+ Phone *string `json:"phone,omitempty"`
+ Username *string `json:"username,omitempty"`
+ AdditionalProperties map[string]string `json:"-"`
}
// SharedReqLogin defines model for _shared_req_Login.
type SharedReqLogin struct {
- ApiKey string `json:"api_key"`
- AppTypeIdentifier string `json:"app_type_identifier"`
- AuthType SharedReqLoginAuthType `json:"auth_type"`
- Creds *SharedReqLogin_Creds `json:"creds,omitempty"`
- Device Device `json:"device"`
- OrgId string `json:"org_id"`
- Params *SharedReqLogin_Params `json:"params,omitempty"`
- Preferences *map[string]interface{} `json:"preferences"`
- Privacy *PrivacyNullable `json:"privacy"`
- Profile *ProfileNullable `json:"profile"`
- Username *string `json:"username"`
+ AccountIdentifierId *string `json:"account_identifier_id"`
+ ApiKey string `json:"api_key"`
+ AppTypeIdentifier string `json:"app_type_identifier"`
+ AuthType SharedReqLoginAuthType `json:"auth_type"`
+ Creds *SharedReqLogin_Creds `json:"creds,omitempty"`
+ Device Device `json:"device"`
+ OrgId string `json:"org_id"`
+ Params *SharedReqLogin_Params `json:"params,omitempty"`
+ Preferences *map[string]interface{} `json:"preferences"`
+ Privacy *PrivacyNullable `json:"privacy"`
+ Profile *ProfileNullable `json:"profile"`
}
// SharedReqLoginAuthType defines model for SharedReqLogin.AuthType.
@@ -1025,13 +1128,6 @@ type SharedReqMfa struct {
// SharedReqMfaType defines model for SharedReqMfa.Type.
type SharedReqMfaType string
-// SharedReqParamsEmail Auth login params for auth_type="email"
-type SharedReqParamsEmail struct {
- // ConfirmPassword This should match the `creds` password field when sign_up=true. This should be verified on the client side as well to reduce invalid requests.
- ConfirmPassword *string `json:"confirm_password,omitempty"`
- SignUp *bool `json:"sign_up,omitempty"`
-}
-
// SharedReqParamsNone Auth login request params for unlisted auth_types (None)
type SharedReqParamsNone = map[string]interface{}
@@ -1041,17 +1137,24 @@ type SharedReqParamsOIDC struct {
RedirectUri *string `json:"redirect_uri,omitempty"`
}
-// SharedReqParamsSetEmailCredential defines model for _shared_req_ParamsSetEmailCredential.
-type SharedReqParamsSetEmailCredential struct {
+// SharedReqParamsPassword Auth login params for auth_type="email"
+type SharedReqParamsPassword struct {
+ // ConfirmPassword This should match the `creds` password field when sign_up=true. This should be verified on the client side as well to reduce invalid requests.
+ ConfirmPassword *string `json:"confirm_password,omitempty"`
+ SignUp *bool `json:"sign_up,omitempty"`
+}
+
+// SharedReqParamsResetPassword defines model for _shared_req_ParamsResetPassword.
+type SharedReqParamsResetPassword struct {
ConfirmPassword string `json:"confirm_password"`
NewPassword string `json:"new_password"`
}
-// SharedReqParamsUsername Auth login params for auth_type="username"
-type SharedReqParamsUsername struct {
- // ConfirmPassword This should match the `creds` password field when sign_up=true. This should be verified on the client side as well to reduce invalid requests.
- ConfirmPassword *string `json:"confirm_password,omitempty"`
- SignUp *bool `json:"sign_up,omitempty"`
+// SharedReqParamsWebAuthn Auth login params for auth_type="webauthn"
+type SharedReqParamsWebAuthn struct {
+ // DisplayName User's account name for display purposes
+ DisplayName *string `json:"display_name,omitempty"`
+ SignUp *bool `json:"sign_up,omitempty"`
}
// SharedReqRefresh defines model for _shared_req_Refresh.
@@ -1062,12 +1165,14 @@ type SharedReqRefresh struct {
// SharedReqUpdateAccount defines model for _shared_req_UpdateAccount.
type SharedReqUpdateAccount struct {
- AuthType SharedReqUpdateAccountAuthType `json:"auth_type"`
- GroupIds *[]string `json:"group_ids,omitempty"`
- Identifier string `json:"identifier"`
- Permissions *[]string `json:"permissions,omitempty"`
- RoleIds *[]string `json:"role_ids,omitempty"`
- Scopes *[]string `json:"scopes,omitempty"`
+ AuthType SharedReqUpdateAccountAuthType `json:"auth_type"`
+ GroupIds *[]string `json:"group_ids,omitempty"`
+
+ // Identifier Allowed identifier types
+ Identifier SharedReqIdentifiers `json:"identifier"`
+ Permissions *[]string `json:"permissions,omitempty"`
+ RoleIds *[]string `json:"role_ids,omitempty"`
+ Scopes *[]string `json:"scopes,omitempty"`
}
// SharedReqUpdateAccountAuthType defines model for SharedReqUpdateAccount.AuthType.
@@ -1135,8 +1240,8 @@ type SharedResMfa struct {
Verified *bool `json:"verified,omitempty"`
}
-// SharedResParamsAPIKey Auth login response params for auth_type="anonymous"
-type SharedResParamsAPIKey struct {
+// SharedResParamsAnonymous Auth login response params for auth_type="anonymous"
+type SharedResParamsAnonymous struct {
AnonymousId *string `json:"anonymous_id,omitempty"`
}
@@ -1180,6 +1285,12 @@ type SharedResRokwireToken struct {
// SharedResRokwireTokenTokenType The type of the provided tokens to be specified when they are sent in the "Authorization" header
type SharedResRokwireTokenTokenType string
+// SharedResSignInOptions defines model for _shared_res_SignInOptions.
+type SharedResSignInOptions struct {
+ AuthTypes []AccountAuthType `json:"auth_types"`
+ Identifiers []AccountIdentifier `json:"identifiers"`
+}
+
// SystemReqUpdateServiceAccount defines model for _system_req_update_service-account.
type SystemReqUpdateServiceAccount struct {
Name *string `json:"name,omitempty"`
@@ -1410,9 +1521,9 @@ type GetServicesAccountsPublicParams struct {
FollowerId *string `form:"follower-id,omitempty" json:"follower-id,omitempty"`
}
-// GetServicesAuthCredentialVerifyParams defines parameters for GetServicesAuthCredentialVerify.
-type GetServicesAuthCredentialVerifyParams struct {
- // Id Credential ID
+// GetServicesAuthIdentifierVerifyParams defines parameters for GetServicesAuthIdentifierVerify.
+type GetServicesAuthIdentifierVerifyParams struct {
+ // Id Account identifier ID
Id string `form:"id" json:"id"`
// Code Verification code
@@ -1595,9 +1706,9 @@ type GetUiCredentialResetParams struct {
Code string `form:"code" json:"code"`
}
-// GetUiCredentialVerifyParams defines parameters for GetUiCredentialVerify.
-type GetUiCredentialVerifyParams struct {
- // Id Credential ID
+// GetUiIdentifierVerifyParams defines parameters for GetUiIdentifierVerify.
+type GetUiIdentifierVerifyParams struct {
+ // Id Identifier ID
Id string `form:"id" json:"id"`
// Code Verification code
@@ -1760,6 +1871,15 @@ type PostServicesAuthAccountCanSignInJSONRequestBody = SharedReqAccountCheck
// PostServicesAuthAccountExistsJSONRequestBody defines body for PostServicesAuthAccountExists for application/json ContentType.
type PostServicesAuthAccountExistsJSONRequestBody = SharedReqAccountCheck
+// DeleteServicesAuthAccountIdentifierLinkJSONRequestBody defines body for DeleteServicesAuthAccountIdentifierLink for application/json ContentType.
+type DeleteServicesAuthAccountIdentifierLinkJSONRequestBody = ServicesReqAccountIdentifierUnlink
+
+// PostServicesAuthAccountIdentifierLinkJSONRequestBody defines body for PostServicesAuthAccountIdentifierLink for application/json ContentType.
+type PostServicesAuthAccountIdentifierLinkJSONRequestBody = ServicesReqAccountIdentifierLink
+
+// PostServicesAuthAccountSignInOptionsJSONRequestBody defines body for PostServicesAuthAccountSignInOptions for application/json ContentType.
+type PostServicesAuthAccountSignInOptionsJSONRequestBody = SharedReqAccountCheck
+
// PostServicesAuthAuthorizeServiceJSONRequestBody defines body for PostServicesAuthAuthorizeService for application/json ContentType.
type PostServicesAuthAuthorizeServiceJSONRequestBody = ServicesReqAuthorizeService
@@ -1770,11 +1890,14 @@ type PostServicesAuthCredentialForgotCompleteJSONRequestBody = ServicesReqCreden
type PostServicesAuthCredentialForgotInitiateJSONRequestBody = ServicesReqCredentialForgotInitiate
// PostServicesAuthCredentialSendVerifyJSONRequestBody defines body for PostServicesAuthCredentialSendVerify for application/json ContentType.
-type PostServicesAuthCredentialSendVerifyJSONRequestBody = ServicesReqCredentialSendVerify
+type PostServicesAuthCredentialSendVerifyJSONRequestBody = ServicesReqIdentifierSendVerify
// PostServicesAuthCredentialUpdateJSONRequestBody defines body for PostServicesAuthCredentialUpdate for application/json ContentType.
type PostServicesAuthCredentialUpdateJSONRequestBody = ServicesReqCredentialUpdate
+// PostServicesAuthIdentifierSendVerifyJSONRequestBody defines body for PostServicesAuthIdentifierSendVerify for application/json ContentType.
+type PostServicesAuthIdentifierSendVerifyJSONRequestBody = ServicesReqIdentifierSendVerify
+
// PostServicesAuthLoginJSONRequestBody defines body for PostServicesAuthLogin for application/json ContentType.
type PostServicesAuthLoginJSONRequestBody = SharedReqLogin
@@ -1859,6 +1982,441 @@ type PostTpsAccountsCountJSONRequestBody = PostTpsAccountsCountJSONBody
// PostTpsServiceAccountIdJSONRequestBody defines body for PostTpsServiceAccountId for application/json ContentType.
type PostTpsServiceAccountIdJSONRequestBody = ServicesReqServiceAccountsParams
+// Getter for additional properties for SharedReqCredsCode. Returns the specified
+// element and whether it was found
+func (a SharedReqCredsCode) Get(fieldName string) (value string, found bool) {
+ if a.AdditionalProperties != nil {
+ value, found = a.AdditionalProperties[fieldName]
+ }
+ return
+}
+
+// Setter for additional properties for SharedReqCredsCode
+func (a *SharedReqCredsCode) Set(fieldName string, value string) {
+ if a.AdditionalProperties == nil {
+ a.AdditionalProperties = make(map[string]string)
+ }
+ a.AdditionalProperties[fieldName] = value
+}
+
+// Override default JSON handling for SharedReqCredsCode to handle AdditionalProperties
+func (a *SharedReqCredsCode) UnmarshalJSON(b []byte) error {
+ object := make(map[string]json.RawMessage)
+ err := json.Unmarshal(b, &object)
+ if err != nil {
+ return err
+ }
+
+ if raw, found := object["code"]; found {
+ err = json.Unmarshal(raw, &a.Code)
+ if err != nil {
+ return fmt.Errorf("error reading 'code': %w", err)
+ }
+ delete(object, "code")
+ }
+
+ if raw, found := object["email"]; found {
+ err = json.Unmarshal(raw, &a.Email)
+ if err != nil {
+ return fmt.Errorf("error reading 'email': %w", err)
+ }
+ delete(object, "email")
+ }
+
+ if raw, found := object["phone"]; found {
+ err = json.Unmarshal(raw, &a.Phone)
+ if err != nil {
+ return fmt.Errorf("error reading 'phone': %w", err)
+ }
+ delete(object, "phone")
+ }
+
+ if raw, found := object["username"]; found {
+ err = json.Unmarshal(raw, &a.Username)
+ if err != nil {
+ return fmt.Errorf("error reading 'username': %w", err)
+ }
+ delete(object, "username")
+ }
+
+ if len(object) != 0 {
+ a.AdditionalProperties = make(map[string]string)
+ for fieldName, fieldBuf := range object {
+ var fieldVal string
+ err := json.Unmarshal(fieldBuf, &fieldVal)
+ if err != nil {
+ return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err)
+ }
+ a.AdditionalProperties[fieldName] = fieldVal
+ }
+ }
+ return nil
+}
+
+// Override default JSON handling for SharedReqCredsCode to handle AdditionalProperties
+func (a SharedReqCredsCode) MarshalJSON() ([]byte, error) {
+ var err error
+ object := make(map[string]json.RawMessage)
+
+ if a.Code != nil {
+ object["code"], err = json.Marshal(a.Code)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling 'code': %w", err)
+ }
+ }
+
+ if a.Email != nil {
+ object["email"], err = json.Marshal(a.Email)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling 'email': %w", err)
+ }
+ }
+
+ if a.Phone != nil {
+ object["phone"], err = json.Marshal(a.Phone)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling 'phone': %w", err)
+ }
+ }
+
+ if a.Username != nil {
+ object["username"], err = json.Marshal(a.Username)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling 'username': %w", err)
+ }
+ }
+
+ for fieldName, field := range a.AdditionalProperties {
+ object[fieldName], err = json.Marshal(field)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err)
+ }
+ }
+ return json.Marshal(object)
+}
+
+// Getter for additional properties for SharedReqCredsPassword. Returns the specified
+// element and whether it was found
+func (a SharedReqCredsPassword) Get(fieldName string) (value string, found bool) {
+ if a.AdditionalProperties != nil {
+ value, found = a.AdditionalProperties[fieldName]
+ }
+ return
+}
+
+// Setter for additional properties for SharedReqCredsPassword
+func (a *SharedReqCredsPassword) Set(fieldName string, value string) {
+ if a.AdditionalProperties == nil {
+ a.AdditionalProperties = make(map[string]string)
+ }
+ a.AdditionalProperties[fieldName] = value
+}
+
+// Override default JSON handling for SharedReqCredsPassword to handle AdditionalProperties
+func (a *SharedReqCredsPassword) UnmarshalJSON(b []byte) error {
+ object := make(map[string]json.RawMessage)
+ err := json.Unmarshal(b, &object)
+ if err != nil {
+ return err
+ }
+
+ if raw, found := object["email"]; found {
+ err = json.Unmarshal(raw, &a.Email)
+ if err != nil {
+ return fmt.Errorf("error reading 'email': %w", err)
+ }
+ delete(object, "email")
+ }
+
+ if raw, found := object["password"]; found {
+ err = json.Unmarshal(raw, &a.Password)
+ if err != nil {
+ return fmt.Errorf("error reading 'password': %w", err)
+ }
+ delete(object, "password")
+ }
+
+ if raw, found := object["phone"]; found {
+ err = json.Unmarshal(raw, &a.Phone)
+ if err != nil {
+ return fmt.Errorf("error reading 'phone': %w", err)
+ }
+ delete(object, "phone")
+ }
+
+ if raw, found := object["username"]; found {
+ err = json.Unmarshal(raw, &a.Username)
+ if err != nil {
+ return fmt.Errorf("error reading 'username': %w", err)
+ }
+ delete(object, "username")
+ }
+
+ if len(object) != 0 {
+ a.AdditionalProperties = make(map[string]string)
+ for fieldName, fieldBuf := range object {
+ var fieldVal string
+ err := json.Unmarshal(fieldBuf, &fieldVal)
+ if err != nil {
+ return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err)
+ }
+ a.AdditionalProperties[fieldName] = fieldVal
+ }
+ }
+ return nil
+}
+
+// Override default JSON handling for SharedReqCredsPassword to handle AdditionalProperties
+func (a SharedReqCredsPassword) MarshalJSON() ([]byte, error) {
+ var err error
+ object := make(map[string]json.RawMessage)
+
+ if a.Email != nil {
+ object["email"], err = json.Marshal(a.Email)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling 'email': %w", err)
+ }
+ }
+
+ object["password"], err = json.Marshal(a.Password)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling 'password': %w", err)
+ }
+
+ if a.Phone != nil {
+ object["phone"], err = json.Marshal(a.Phone)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling 'phone': %w", err)
+ }
+ }
+
+ if a.Username != nil {
+ object["username"], err = json.Marshal(a.Username)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling 'username': %w", err)
+ }
+ }
+
+ for fieldName, field := range a.AdditionalProperties {
+ object[fieldName], err = json.Marshal(field)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err)
+ }
+ }
+ return json.Marshal(object)
+}
+
+// Getter for additional properties for SharedReqCredsWebAuthn. Returns the specified
+// element and whether it was found
+func (a SharedReqCredsWebAuthn) Get(fieldName string) (value string, found bool) {
+ if a.AdditionalProperties != nil {
+ value, found = a.AdditionalProperties[fieldName]
+ }
+ return
+}
+
+// Setter for additional properties for SharedReqCredsWebAuthn
+func (a *SharedReqCredsWebAuthn) Set(fieldName string, value string) {
+ if a.AdditionalProperties == nil {
+ a.AdditionalProperties = make(map[string]string)
+ }
+ a.AdditionalProperties[fieldName] = value
+}
+
+// Override default JSON handling for SharedReqCredsWebAuthn to handle AdditionalProperties
+func (a *SharedReqCredsWebAuthn) UnmarshalJSON(b []byte) error {
+ object := make(map[string]json.RawMessage)
+ err := json.Unmarshal(b, &object)
+ if err != nil {
+ return err
+ }
+
+ if raw, found := object["email"]; found {
+ err = json.Unmarshal(raw, &a.Email)
+ if err != nil {
+ return fmt.Errorf("error reading 'email': %w", err)
+ }
+ delete(object, "email")
+ }
+
+ if raw, found := object["phone"]; found {
+ err = json.Unmarshal(raw, &a.Phone)
+ if err != nil {
+ return fmt.Errorf("error reading 'phone': %w", err)
+ }
+ delete(object, "phone")
+ }
+
+ if raw, found := object["response"]; found {
+ err = json.Unmarshal(raw, &a.Response)
+ if err != nil {
+ return fmt.Errorf("error reading 'response': %w", err)
+ }
+ delete(object, "response")
+ }
+
+ if raw, found := object["username"]; found {
+ err = json.Unmarshal(raw, &a.Username)
+ if err != nil {
+ return fmt.Errorf("error reading 'username': %w", err)
+ }
+ delete(object, "username")
+ }
+
+ if len(object) != 0 {
+ a.AdditionalProperties = make(map[string]string)
+ for fieldName, fieldBuf := range object {
+ var fieldVal string
+ err := json.Unmarshal(fieldBuf, &fieldVal)
+ if err != nil {
+ return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err)
+ }
+ a.AdditionalProperties[fieldName] = fieldVal
+ }
+ }
+ return nil
+}
+
+// Override default JSON handling for SharedReqCredsWebAuthn to handle AdditionalProperties
+func (a SharedReqCredsWebAuthn) MarshalJSON() ([]byte, error) {
+ var err error
+ object := make(map[string]json.RawMessage)
+
+ if a.Email != nil {
+ object["email"], err = json.Marshal(a.Email)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling 'email': %w", err)
+ }
+ }
+
+ if a.Phone != nil {
+ object["phone"], err = json.Marshal(a.Phone)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling 'phone': %w", err)
+ }
+ }
+
+ if a.Response != nil {
+ object["response"], err = json.Marshal(a.Response)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling 'response': %w", err)
+ }
+ }
+
+ if a.Username != nil {
+ object["username"], err = json.Marshal(a.Username)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling 'username': %w", err)
+ }
+ }
+
+ for fieldName, field := range a.AdditionalProperties {
+ object[fieldName], err = json.Marshal(field)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err)
+ }
+ }
+ return json.Marshal(object)
+}
+
+// Getter for additional properties for SharedReqIdentifiers. Returns the specified
+// element and whether it was found
+func (a SharedReqIdentifiers) Get(fieldName string) (value string, found bool) {
+ if a.AdditionalProperties != nil {
+ value, found = a.AdditionalProperties[fieldName]
+ }
+ return
+}
+
+// Setter for additional properties for SharedReqIdentifiers
+func (a *SharedReqIdentifiers) Set(fieldName string, value string) {
+ if a.AdditionalProperties == nil {
+ a.AdditionalProperties = make(map[string]string)
+ }
+ a.AdditionalProperties[fieldName] = value
+}
+
+// Override default JSON handling for SharedReqIdentifiers to handle AdditionalProperties
+func (a *SharedReqIdentifiers) UnmarshalJSON(b []byte) error {
+ object := make(map[string]json.RawMessage)
+ err := json.Unmarshal(b, &object)
+ if err != nil {
+ return err
+ }
+
+ if raw, found := object["email"]; found {
+ err = json.Unmarshal(raw, &a.Email)
+ if err != nil {
+ return fmt.Errorf("error reading 'email': %w", err)
+ }
+ delete(object, "email")
+ }
+
+ if raw, found := object["phone"]; found {
+ err = json.Unmarshal(raw, &a.Phone)
+ if err != nil {
+ return fmt.Errorf("error reading 'phone': %w", err)
+ }
+ delete(object, "phone")
+ }
+
+ if raw, found := object["username"]; found {
+ err = json.Unmarshal(raw, &a.Username)
+ if err != nil {
+ return fmt.Errorf("error reading 'username': %w", err)
+ }
+ delete(object, "username")
+ }
+
+ if len(object) != 0 {
+ a.AdditionalProperties = make(map[string]string)
+ for fieldName, fieldBuf := range object {
+ var fieldVal string
+ err := json.Unmarshal(fieldBuf, &fieldVal)
+ if err != nil {
+ return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err)
+ }
+ a.AdditionalProperties[fieldName] = fieldVal
+ }
+ }
+ return nil
+}
+
+// Override default JSON handling for SharedReqIdentifiers to handle AdditionalProperties
+func (a SharedReqIdentifiers) MarshalJSON() ([]byte, error) {
+ var err error
+ object := make(map[string]json.RawMessage)
+
+ if a.Email != nil {
+ object["email"], err = json.Marshal(a.Email)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling 'email': %w", err)
+ }
+ }
+
+ if a.Phone != nil {
+ object["phone"], err = json.Marshal(a.Phone)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling 'phone': %w", err)
+ }
+ }
+
+ if a.Username != nil {
+ object["username"], err = json.Marshal(a.Username)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling 'username': %w", err)
+ }
+ }
+
+ for fieldName, field := range a.AdditionalProperties {
+ object[fieldName], err = json.Marshal(field)
+ if err != nil {
+ return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err)
+ }
+ }
+ return json.Marshal(object)
+}
+
// AsEnvConfigData returns the union data inside the Config_Data as a EnvConfigData
func (t Config_Data) AsEnvConfigData() (EnvConfigData, error) {
var body EnvConfigData
@@ -1885,6 +2443,32 @@ func (t *Config_Data) MergeEnvConfigData(v EnvConfigData) error {
return err
}
+// AsAuthConfigData returns the union data inside the Config_Data as a AuthConfigData
+func (t Config_Data) AsAuthConfigData() (AuthConfigData, error) {
+ var body AuthConfigData
+ err := json.Unmarshal(t.union, &body)
+ return body, err
+}
+
+// FromAuthConfigData overwrites any union data inside the Config_Data as the provided AuthConfigData
+func (t *Config_Data) FromAuthConfigData(v AuthConfigData) error {
+ b, err := json.Marshal(v)
+ t.union = b
+ return err
+}
+
+// MergeAuthConfigData performs a merge with any union data inside the Config_Data, using the provided AuthConfigData
+func (t *Config_Data) MergeAuthConfigData(v AuthConfigData) error {
+ b, err := json.Marshal(v)
+ if err != nil {
+ return err
+ }
+
+ merged, err := runtime.JsonMerge(t.union, b)
+ t.union = merged
+ return err
+}
+
func (t Config_Data) MarshalJSON() ([]byte, error) {
b, err := t.union.MarshalJSON()
return b, err
@@ -1921,6 +2505,32 @@ func (t *AdminReqCreateUpdateConfig_Data) MergeEnvConfigData(v EnvConfigData) er
return err
}
+// AsAuthConfigData returns the union data inside the AdminReqCreateUpdateConfig_Data as a AuthConfigData
+func (t AdminReqCreateUpdateConfig_Data) AsAuthConfigData() (AuthConfigData, error) {
+ var body AuthConfigData
+ err := json.Unmarshal(t.union, &body)
+ return body, err
+}
+
+// FromAuthConfigData overwrites any union data inside the AdminReqCreateUpdateConfig_Data as the provided AuthConfigData
+func (t *AdminReqCreateUpdateConfig_Data) FromAuthConfigData(v AuthConfigData) error {
+ b, err := json.Marshal(v)
+ t.union = b
+ return err
+}
+
+// MergeAuthConfigData performs a merge with any union data inside the AdminReqCreateUpdateConfig_Data, using the provided AuthConfigData
+func (t *AdminReqCreateUpdateConfig_Data) MergeAuthConfigData(v AuthConfigData) error {
+ b, err := json.Marshal(v)
+ if err != nil {
+ return err
+ }
+
+ merged, err := runtime.JsonMerge(t.union, b)
+ t.union = merged
+ return err
+}
+
func (t AdminReqCreateUpdateConfig_Data) MarshalJSON() ([]byte, error) {
b, err := t.union.MarshalJSON()
return b, err
@@ -1931,22 +2541,22 @@ func (t *AdminReqCreateUpdateConfig_Data) UnmarshalJSON(b []byte) error {
return err
}
-// AsSharedReqCredsEmail returns the union data inside the ServicesReqAccountAuthTypeLink_Creds as a SharedReqCredsEmail
-func (t ServicesReqAccountAuthTypeLink_Creds) AsSharedReqCredsEmail() (SharedReqCredsEmail, error) {
- var body SharedReqCredsEmail
+// AsSharedReqCredsCode returns the union data inside the ServicesReqAccountAuthTypeLink_Creds as a SharedReqCredsCode
+func (t ServicesReqAccountAuthTypeLink_Creds) AsSharedReqCredsCode() (SharedReqCredsCode, error) {
+ var body SharedReqCredsCode
err := json.Unmarshal(t.union, &body)
return body, err
}
-// FromSharedReqCredsEmail overwrites any union data inside the ServicesReqAccountAuthTypeLink_Creds as the provided SharedReqCredsEmail
-func (t *ServicesReqAccountAuthTypeLink_Creds) FromSharedReqCredsEmail(v SharedReqCredsEmail) error {
+// FromSharedReqCredsCode overwrites any union data inside the ServicesReqAccountAuthTypeLink_Creds as the provided SharedReqCredsCode
+func (t *ServicesReqAccountAuthTypeLink_Creds) FromSharedReqCredsCode(v SharedReqCredsCode) error {
b, err := json.Marshal(v)
t.union = b
return err
}
-// MergeSharedReqCredsEmail performs a merge with any union data inside the ServicesReqAccountAuthTypeLink_Creds, using the provided SharedReqCredsEmail
-func (t *ServicesReqAccountAuthTypeLink_Creds) MergeSharedReqCredsEmail(v SharedReqCredsEmail) error {
+// MergeSharedReqCredsCode performs a merge with any union data inside the ServicesReqAccountAuthTypeLink_Creds, using the provided SharedReqCredsCode
+func (t *ServicesReqAccountAuthTypeLink_Creds) MergeSharedReqCredsCode(v SharedReqCredsCode) error {
b, err := json.Marshal(v)
if err != nil {
return err
@@ -1957,22 +2567,22 @@ func (t *ServicesReqAccountAuthTypeLink_Creds) MergeSharedReqCredsEmail(v Shared
return err
}
-// AsSharedReqCredsTwilioPhone returns the union data inside the ServicesReqAccountAuthTypeLink_Creds as a SharedReqCredsTwilioPhone
-func (t ServicesReqAccountAuthTypeLink_Creds) AsSharedReqCredsTwilioPhone() (SharedReqCredsTwilioPhone, error) {
- var body SharedReqCredsTwilioPhone
+// AsSharedReqCredsOIDC returns the union data inside the ServicesReqAccountAuthTypeLink_Creds as a SharedReqCredsOIDC
+func (t ServicesReqAccountAuthTypeLink_Creds) AsSharedReqCredsOIDC() (SharedReqCredsOIDC, error) {
+ var body SharedReqCredsOIDC
err := json.Unmarshal(t.union, &body)
return body, err
}
-// FromSharedReqCredsTwilioPhone overwrites any union data inside the ServicesReqAccountAuthTypeLink_Creds as the provided SharedReqCredsTwilioPhone
-func (t *ServicesReqAccountAuthTypeLink_Creds) FromSharedReqCredsTwilioPhone(v SharedReqCredsTwilioPhone) error {
+// FromSharedReqCredsOIDC overwrites any union data inside the ServicesReqAccountAuthTypeLink_Creds as the provided SharedReqCredsOIDC
+func (t *ServicesReqAccountAuthTypeLink_Creds) FromSharedReqCredsOIDC(v SharedReqCredsOIDC) error {
b, err := json.Marshal(v)
t.union = b
return err
}
-// MergeSharedReqCredsTwilioPhone performs a merge with any union data inside the ServicesReqAccountAuthTypeLink_Creds, using the provided SharedReqCredsTwilioPhone
-func (t *ServicesReqAccountAuthTypeLink_Creds) MergeSharedReqCredsTwilioPhone(v SharedReqCredsTwilioPhone) error {
+// MergeSharedReqCredsOIDC performs a merge with any union data inside the ServicesReqAccountAuthTypeLink_Creds, using the provided SharedReqCredsOIDC
+func (t *ServicesReqAccountAuthTypeLink_Creds) MergeSharedReqCredsOIDC(v SharedReqCredsOIDC) error {
b, err := json.Marshal(v)
if err != nil {
return err
@@ -1983,22 +2593,22 @@ func (t *ServicesReqAccountAuthTypeLink_Creds) MergeSharedReqCredsTwilioPhone(v
return err
}
-// AsSharedReqCredsOIDC returns the union data inside the ServicesReqAccountAuthTypeLink_Creds as a SharedReqCredsOIDC
-func (t ServicesReqAccountAuthTypeLink_Creds) AsSharedReqCredsOIDC() (SharedReqCredsOIDC, error) {
- var body SharedReqCredsOIDC
+// AsSharedReqCredsPassword returns the union data inside the ServicesReqAccountAuthTypeLink_Creds as a SharedReqCredsPassword
+func (t ServicesReqAccountAuthTypeLink_Creds) AsSharedReqCredsPassword() (SharedReqCredsPassword, error) {
+ var body SharedReqCredsPassword
err := json.Unmarshal(t.union, &body)
return body, err
}
-// FromSharedReqCredsOIDC overwrites any union data inside the ServicesReqAccountAuthTypeLink_Creds as the provided SharedReqCredsOIDC
-func (t *ServicesReqAccountAuthTypeLink_Creds) FromSharedReqCredsOIDC(v SharedReqCredsOIDC) error {
+// FromSharedReqCredsPassword overwrites any union data inside the ServicesReqAccountAuthTypeLink_Creds as the provided SharedReqCredsPassword
+func (t *ServicesReqAccountAuthTypeLink_Creds) FromSharedReqCredsPassword(v SharedReqCredsPassword) error {
b, err := json.Marshal(v)
t.union = b
return err
}
-// MergeSharedReqCredsOIDC performs a merge with any union data inside the ServicesReqAccountAuthTypeLink_Creds, using the provided SharedReqCredsOIDC
-func (t *ServicesReqAccountAuthTypeLink_Creds) MergeSharedReqCredsOIDC(v SharedReqCredsOIDC) error {
+// MergeSharedReqCredsPassword performs a merge with any union data inside the ServicesReqAccountAuthTypeLink_Creds, using the provided SharedReqCredsPassword
+func (t *ServicesReqAccountAuthTypeLink_Creds) MergeSharedReqCredsPassword(v SharedReqCredsPassword) error {
b, err := json.Marshal(v)
if err != nil {
return err
@@ -2009,32 +2619,48 @@ func (t *ServicesReqAccountAuthTypeLink_Creds) MergeSharedReqCredsOIDC(v SharedR
return err
}
-func (t ServicesReqAccountAuthTypeLink_Creds) MarshalJSON() ([]byte, error) {
- b, err := t.union.MarshalJSON()
- return b, err
+// AsSharedReqCredsWebAuthn returns the union data inside the ServicesReqAccountAuthTypeLink_Creds as a SharedReqCredsWebAuthn
+func (t ServicesReqAccountAuthTypeLink_Creds) AsSharedReqCredsWebAuthn() (SharedReqCredsWebAuthn, error) {
+ var body SharedReqCredsWebAuthn
+ err := json.Unmarshal(t.union, &body)
+ return body, err
}
-func (t *ServicesReqAccountAuthTypeLink_Creds) UnmarshalJSON(b []byte) error {
- err := t.union.UnmarshalJSON(b)
+// FromSharedReqCredsWebAuthn overwrites any union data inside the ServicesReqAccountAuthTypeLink_Creds as the provided SharedReqCredsWebAuthn
+func (t *ServicesReqAccountAuthTypeLink_Creds) FromSharedReqCredsWebAuthn(v SharedReqCredsWebAuthn) error {
+ b, err := json.Marshal(v)
+ t.union = b
return err
}
-// AsSharedReqParamsEmail returns the union data inside the ServicesReqAccountAuthTypeLink_Params as a SharedReqParamsEmail
-func (t ServicesReqAccountAuthTypeLink_Params) AsSharedReqParamsEmail() (SharedReqParamsEmail, error) {
- var body SharedReqParamsEmail
+// MergeSharedReqCredsWebAuthn performs a merge with any union data inside the ServicesReqAccountAuthTypeLink_Creds, using the provided SharedReqCredsWebAuthn
+func (t *ServicesReqAccountAuthTypeLink_Creds) MergeSharedReqCredsWebAuthn(v SharedReqCredsWebAuthn) error {
+ b, err := json.Marshal(v)
+ if err != nil {
+ return err
+ }
+
+ merged, err := runtime.JsonMerge(t.union, b)
+ t.union = merged
+ return err
+}
+
+// AsSharedReqCredsNone returns the union data inside the ServicesReqAccountAuthTypeLink_Creds as a SharedReqCredsNone
+func (t ServicesReqAccountAuthTypeLink_Creds) AsSharedReqCredsNone() (SharedReqCredsNone, error) {
+ var body SharedReqCredsNone
err := json.Unmarshal(t.union, &body)
return body, err
}
-// FromSharedReqParamsEmail overwrites any union data inside the ServicesReqAccountAuthTypeLink_Params as the provided SharedReqParamsEmail
-func (t *ServicesReqAccountAuthTypeLink_Params) FromSharedReqParamsEmail(v SharedReqParamsEmail) error {
+// FromSharedReqCredsNone overwrites any union data inside the ServicesReqAccountAuthTypeLink_Creds as the provided SharedReqCredsNone
+func (t *ServicesReqAccountAuthTypeLink_Creds) FromSharedReqCredsNone(v SharedReqCredsNone) error {
b, err := json.Marshal(v)
t.union = b
return err
}
-// MergeSharedReqParamsEmail performs a merge with any union data inside the ServicesReqAccountAuthTypeLink_Params, using the provided SharedReqParamsEmail
-func (t *ServicesReqAccountAuthTypeLink_Params) MergeSharedReqParamsEmail(v SharedReqParamsEmail) error {
+// MergeSharedReqCredsNone performs a merge with any union data inside the ServicesReqAccountAuthTypeLink_Creds, using the provided SharedReqCredsNone
+func (t *ServicesReqAccountAuthTypeLink_Creds) MergeSharedReqCredsNone(v SharedReqCredsNone) error {
b, err := json.Marshal(v)
if err != nil {
return err
@@ -2045,6 +2671,16 @@ func (t *ServicesReqAccountAuthTypeLink_Params) MergeSharedReqParamsEmail(v Shar
return err
}
+func (t ServicesReqAccountAuthTypeLink_Creds) MarshalJSON() ([]byte, error) {
+ b, err := t.union.MarshalJSON()
+ return b, err
+}
+
+func (t *ServicesReqAccountAuthTypeLink_Creds) UnmarshalJSON(b []byte) error {
+ err := t.union.UnmarshalJSON(b)
+ return err
+}
+
// AsSharedReqParamsOIDC returns the union data inside the ServicesReqAccountAuthTypeLink_Params as a SharedReqParamsOIDC
func (t ServicesReqAccountAuthTypeLink_Params) AsSharedReqParamsOIDC() (SharedReqParamsOIDC, error) {
var body SharedReqParamsOIDC
@@ -2071,6 +2707,58 @@ func (t *ServicesReqAccountAuthTypeLink_Params) MergeSharedReqParamsOIDC(v Share
return err
}
+// AsSharedReqParamsPassword returns the union data inside the ServicesReqAccountAuthTypeLink_Params as a SharedReqParamsPassword
+func (t ServicesReqAccountAuthTypeLink_Params) AsSharedReqParamsPassword() (SharedReqParamsPassword, error) {
+ var body SharedReqParamsPassword
+ err := json.Unmarshal(t.union, &body)
+ return body, err
+}
+
+// FromSharedReqParamsPassword overwrites any union data inside the ServicesReqAccountAuthTypeLink_Params as the provided SharedReqParamsPassword
+func (t *ServicesReqAccountAuthTypeLink_Params) FromSharedReqParamsPassword(v SharedReqParamsPassword) error {
+ b, err := json.Marshal(v)
+ t.union = b
+ return err
+}
+
+// MergeSharedReqParamsPassword performs a merge with any union data inside the ServicesReqAccountAuthTypeLink_Params, using the provided SharedReqParamsPassword
+func (t *ServicesReqAccountAuthTypeLink_Params) MergeSharedReqParamsPassword(v SharedReqParamsPassword) error {
+ b, err := json.Marshal(v)
+ if err != nil {
+ return err
+ }
+
+ merged, err := runtime.JsonMerge(t.union, b)
+ t.union = merged
+ return err
+}
+
+// AsSharedReqParamsWebAuthn returns the union data inside the ServicesReqAccountAuthTypeLink_Params as a SharedReqParamsWebAuthn
+func (t ServicesReqAccountAuthTypeLink_Params) AsSharedReqParamsWebAuthn() (SharedReqParamsWebAuthn, error) {
+ var body SharedReqParamsWebAuthn
+ err := json.Unmarshal(t.union, &body)
+ return body, err
+}
+
+// FromSharedReqParamsWebAuthn overwrites any union data inside the ServicesReqAccountAuthTypeLink_Params as the provided SharedReqParamsWebAuthn
+func (t *ServicesReqAccountAuthTypeLink_Params) FromSharedReqParamsWebAuthn(v SharedReqParamsWebAuthn) error {
+ b, err := json.Marshal(v)
+ t.union = b
+ return err
+}
+
+// MergeSharedReqParamsWebAuthn performs a merge with any union data inside the ServicesReqAccountAuthTypeLink_Params, using the provided SharedReqParamsWebAuthn
+func (t *ServicesReqAccountAuthTypeLink_Params) MergeSharedReqParamsWebAuthn(v SharedReqParamsWebAuthn) error {
+ b, err := json.Marshal(v)
+ if err != nil {
+ return err
+ }
+
+ merged, err := runtime.JsonMerge(t.union, b)
+ t.union = merged
+ return err
+}
+
// AsSharedReqParamsNone returns the union data inside the ServicesReqAccountAuthTypeLink_Params as a SharedReqParamsNone
func (t ServicesReqAccountAuthTypeLink_Params) AsSharedReqParamsNone() (SharedReqParamsNone, error) {
var body SharedReqParamsNone
@@ -2107,22 +2795,22 @@ func (t *ServicesReqAccountAuthTypeLink_Params) UnmarshalJSON(b []byte) error {
return err
}
-// AsSharedReqParamsSetEmailCredential returns the union data inside the ServicesReqCredentialForgotComplete_Params as a SharedReqParamsSetEmailCredential
-func (t ServicesReqCredentialForgotComplete_Params) AsSharedReqParamsSetEmailCredential() (SharedReqParamsSetEmailCredential, error) {
- var body SharedReqParamsSetEmailCredential
+// AsSharedReqParamsResetPassword returns the union data inside the ServicesReqCredentialForgotComplete_Params as a SharedReqParamsResetPassword
+func (t ServicesReqCredentialForgotComplete_Params) AsSharedReqParamsResetPassword() (SharedReqParamsResetPassword, error) {
+ var body SharedReqParamsResetPassword
err := json.Unmarshal(t.union, &body)
return body, err
}
-// FromSharedReqParamsSetEmailCredential overwrites any union data inside the ServicesReqCredentialForgotComplete_Params as the provided SharedReqParamsSetEmailCredential
-func (t *ServicesReqCredentialForgotComplete_Params) FromSharedReqParamsSetEmailCredential(v SharedReqParamsSetEmailCredential) error {
+// FromSharedReqParamsResetPassword overwrites any union data inside the ServicesReqCredentialForgotComplete_Params as the provided SharedReqParamsResetPassword
+func (t *ServicesReqCredentialForgotComplete_Params) FromSharedReqParamsResetPassword(v SharedReqParamsResetPassword) error {
b, err := json.Marshal(v)
t.union = b
return err
}
-// MergeSharedReqParamsSetEmailCredential performs a merge with any union data inside the ServicesReqCredentialForgotComplete_Params, using the provided SharedReqParamsSetEmailCredential
-func (t *ServicesReqCredentialForgotComplete_Params) MergeSharedReqParamsSetEmailCredential(v SharedReqParamsSetEmailCredential) error {
+// MergeSharedReqParamsResetPassword performs a merge with any union data inside the ServicesReqCredentialForgotComplete_Params, using the provided SharedReqParamsResetPassword
+func (t *ServicesReqCredentialForgotComplete_Params) MergeSharedReqParamsResetPassword(v SharedReqParamsResetPassword) error {
b, err := json.Marshal(v)
if err != nil {
return err
@@ -2143,22 +2831,84 @@ func (t *ServicesReqCredentialForgotComplete_Params) UnmarshalJSON(b []byte) err
return err
}
-// AsSharedReqParamsSetEmailCredential returns the union data inside the ServicesReqCredentialUpdate_Params as a SharedReqParamsSetEmailCredential
-func (t ServicesReqCredentialUpdate_Params) AsSharedReqParamsSetEmailCredential() (SharedReqParamsSetEmailCredential, error) {
- var body SharedReqParamsSetEmailCredential
+// AsSharedReqIdentifiers returns the union data inside the ServicesReqCredentialForgotInitiate_Identifier as a SharedReqIdentifiers
+func (t ServicesReqCredentialForgotInitiate_Identifier) AsSharedReqIdentifiers() (SharedReqIdentifiers, error) {
+ var body SharedReqIdentifiers
+ err := json.Unmarshal(t.union, &body)
+ return body, err
+}
+
+// FromSharedReqIdentifiers overwrites any union data inside the ServicesReqCredentialForgotInitiate_Identifier as the provided SharedReqIdentifiers
+func (t *ServicesReqCredentialForgotInitiate_Identifier) FromSharedReqIdentifiers(v SharedReqIdentifiers) error {
+ b, err := json.Marshal(v)
+ t.union = b
+ return err
+}
+
+// MergeSharedReqIdentifiers performs a merge with any union data inside the ServicesReqCredentialForgotInitiate_Identifier, using the provided SharedReqIdentifiers
+func (t *ServicesReqCredentialForgotInitiate_Identifier) MergeSharedReqIdentifiers(v SharedReqIdentifiers) error {
+ b, err := json.Marshal(v)
+ if err != nil {
+ return err
+ }
+
+ merged, err := runtime.JsonMerge(t.union, b)
+ t.union = merged
+ return err
+}
+
+// AsSharedReqIdentifierString returns the union data inside the ServicesReqCredentialForgotInitiate_Identifier as a SharedReqIdentifierString
+func (t ServicesReqCredentialForgotInitiate_Identifier) AsSharedReqIdentifierString() (SharedReqIdentifierString, error) {
+ var body SharedReqIdentifierString
+ err := json.Unmarshal(t.union, &body)
+ return body, err
+}
+
+// FromSharedReqIdentifierString overwrites any union data inside the ServicesReqCredentialForgotInitiate_Identifier as the provided SharedReqIdentifierString
+func (t *ServicesReqCredentialForgotInitiate_Identifier) FromSharedReqIdentifierString(v SharedReqIdentifierString) error {
+ b, err := json.Marshal(v)
+ t.union = b
+ return err
+}
+
+// MergeSharedReqIdentifierString performs a merge with any union data inside the ServicesReqCredentialForgotInitiate_Identifier, using the provided SharedReqIdentifierString
+func (t *ServicesReqCredentialForgotInitiate_Identifier) MergeSharedReqIdentifierString(v SharedReqIdentifierString) error {
+ b, err := json.Marshal(v)
+ if err != nil {
+ return err
+ }
+
+ merged, err := runtime.JsonMerge(t.union, b)
+ t.union = merged
+ return err
+}
+
+func (t ServicesReqCredentialForgotInitiate_Identifier) MarshalJSON() ([]byte, error) {
+ b, err := t.union.MarshalJSON()
+ return b, err
+}
+
+func (t *ServicesReqCredentialForgotInitiate_Identifier) UnmarshalJSON(b []byte) error {
+ err := t.union.UnmarshalJSON(b)
+ return err
+}
+
+// AsSharedReqParamsResetPassword returns the union data inside the ServicesReqCredentialUpdate_Params as a SharedReqParamsResetPassword
+func (t ServicesReqCredentialUpdate_Params) AsSharedReqParamsResetPassword() (SharedReqParamsResetPassword, error) {
+ var body SharedReqParamsResetPassword
err := json.Unmarshal(t.union, &body)
return body, err
}
-// FromSharedReqParamsSetEmailCredential overwrites any union data inside the ServicesReqCredentialUpdate_Params as the provided SharedReqParamsSetEmailCredential
-func (t *ServicesReqCredentialUpdate_Params) FromSharedReqParamsSetEmailCredential(v SharedReqParamsSetEmailCredential) error {
+// FromSharedReqParamsResetPassword overwrites any union data inside the ServicesReqCredentialUpdate_Params as the provided SharedReqParamsResetPassword
+func (t *ServicesReqCredentialUpdate_Params) FromSharedReqParamsResetPassword(v SharedReqParamsResetPassword) error {
b, err := json.Marshal(v)
t.union = b
return err
}
-// MergeSharedReqParamsSetEmailCredential performs a merge with any union data inside the ServicesReqCredentialUpdate_Params, using the provided SharedReqParamsSetEmailCredential
-func (t *ServicesReqCredentialUpdate_Params) MergeSharedReqParamsSetEmailCredential(v SharedReqParamsSetEmailCredential) error {
+// MergeSharedReqParamsResetPassword performs a merge with any union data inside the ServicesReqCredentialUpdate_Params, using the provided SharedReqParamsResetPassword
+func (t *ServicesReqCredentialUpdate_Params) MergeSharedReqParamsResetPassword(v SharedReqParamsResetPassword) error {
b, err := json.Marshal(v)
if err != nil {
return err
@@ -2179,22 +2929,22 @@ func (t *ServicesReqCredentialUpdate_Params) UnmarshalJSON(b []byte) error {
return err
}
-// AsSharedReqCredsEmail returns the union data inside the SharedReqLogin_Creds as a SharedReqCredsEmail
-func (t SharedReqLogin_Creds) AsSharedReqCredsEmail() (SharedReqCredsEmail, error) {
- var body SharedReqCredsEmail
+// AsSharedReqIdentifiers returns the union data inside the ServicesReqIdentifierSendVerify_Identifier as a SharedReqIdentifiers
+func (t ServicesReqIdentifierSendVerify_Identifier) AsSharedReqIdentifiers() (SharedReqIdentifiers, error) {
+ var body SharedReqIdentifiers
err := json.Unmarshal(t.union, &body)
return body, err
}
-// FromSharedReqCredsEmail overwrites any union data inside the SharedReqLogin_Creds as the provided SharedReqCredsEmail
-func (t *SharedReqLogin_Creds) FromSharedReqCredsEmail(v SharedReqCredsEmail) error {
+// FromSharedReqIdentifiers overwrites any union data inside the ServicesReqIdentifierSendVerify_Identifier as the provided SharedReqIdentifiers
+func (t *ServicesReqIdentifierSendVerify_Identifier) FromSharedReqIdentifiers(v SharedReqIdentifiers) error {
b, err := json.Marshal(v)
t.union = b
return err
}
-// MergeSharedReqCredsEmail performs a merge with any union data inside the SharedReqLogin_Creds, using the provided SharedReqCredsEmail
-func (t *SharedReqLogin_Creds) MergeSharedReqCredsEmail(v SharedReqCredsEmail) error {
+// MergeSharedReqIdentifiers performs a merge with any union data inside the ServicesReqIdentifierSendVerify_Identifier, using the provided SharedReqIdentifiers
+func (t *ServicesReqIdentifierSendVerify_Identifier) MergeSharedReqIdentifiers(v SharedReqIdentifiers) error {
b, err := json.Marshal(v)
if err != nil {
return err
@@ -2205,22 +2955,84 @@ func (t *SharedReqLogin_Creds) MergeSharedReqCredsEmail(v SharedReqCredsEmail) e
return err
}
-// AsSharedReqCredsTwilioPhone returns the union data inside the SharedReqLogin_Creds as a SharedReqCredsTwilioPhone
-func (t SharedReqLogin_Creds) AsSharedReqCredsTwilioPhone() (SharedReqCredsTwilioPhone, error) {
- var body SharedReqCredsTwilioPhone
+// AsSharedReqIdentifierString returns the union data inside the ServicesReqIdentifierSendVerify_Identifier as a SharedReqIdentifierString
+func (t ServicesReqIdentifierSendVerify_Identifier) AsSharedReqIdentifierString() (SharedReqIdentifierString, error) {
+ var body SharedReqIdentifierString
err := json.Unmarshal(t.union, &body)
return body, err
}
-// FromSharedReqCredsTwilioPhone overwrites any union data inside the SharedReqLogin_Creds as the provided SharedReqCredsTwilioPhone
-func (t *SharedReqLogin_Creds) FromSharedReqCredsTwilioPhone(v SharedReqCredsTwilioPhone) error {
+// FromSharedReqIdentifierString overwrites any union data inside the ServicesReqIdentifierSendVerify_Identifier as the provided SharedReqIdentifierString
+func (t *ServicesReqIdentifierSendVerify_Identifier) FromSharedReqIdentifierString(v SharedReqIdentifierString) error {
b, err := json.Marshal(v)
t.union = b
return err
}
-// MergeSharedReqCredsTwilioPhone performs a merge with any union data inside the SharedReqLogin_Creds, using the provided SharedReqCredsTwilioPhone
-func (t *SharedReqLogin_Creds) MergeSharedReqCredsTwilioPhone(v SharedReqCredsTwilioPhone) error {
+// MergeSharedReqIdentifierString performs a merge with any union data inside the ServicesReqIdentifierSendVerify_Identifier, using the provided SharedReqIdentifierString
+func (t *ServicesReqIdentifierSendVerify_Identifier) MergeSharedReqIdentifierString(v SharedReqIdentifierString) error {
+ b, err := json.Marshal(v)
+ if err != nil {
+ return err
+ }
+
+ merged, err := runtime.JsonMerge(t.union, b)
+ t.union = merged
+ return err
+}
+
+func (t ServicesReqIdentifierSendVerify_Identifier) MarshalJSON() ([]byte, error) {
+ b, err := t.union.MarshalJSON()
+ return b, err
+}
+
+func (t *ServicesReqIdentifierSendVerify_Identifier) UnmarshalJSON(b []byte) error {
+ err := t.union.UnmarshalJSON(b)
+ return err
+}
+
+// AsSharedReqCredsAnonymous returns the union data inside the SharedReqLogin_Creds as a SharedReqCredsAnonymous
+func (t SharedReqLogin_Creds) AsSharedReqCredsAnonymous() (SharedReqCredsAnonymous, error) {
+ var body SharedReqCredsAnonymous
+ err := json.Unmarshal(t.union, &body)
+ return body, err
+}
+
+// FromSharedReqCredsAnonymous overwrites any union data inside the SharedReqLogin_Creds as the provided SharedReqCredsAnonymous
+func (t *SharedReqLogin_Creds) FromSharedReqCredsAnonymous(v SharedReqCredsAnonymous) error {
+ b, err := json.Marshal(v)
+ t.union = b
+ return err
+}
+
+// MergeSharedReqCredsAnonymous performs a merge with any union data inside the SharedReqLogin_Creds, using the provided SharedReqCredsAnonymous
+func (t *SharedReqLogin_Creds) MergeSharedReqCredsAnonymous(v SharedReqCredsAnonymous) error {
+ b, err := json.Marshal(v)
+ if err != nil {
+ return err
+ }
+
+ merged, err := runtime.JsonMerge(t.union, b)
+ t.union = merged
+ return err
+}
+
+// AsSharedReqCredsCode returns the union data inside the SharedReqLogin_Creds as a SharedReqCredsCode
+func (t SharedReqLogin_Creds) AsSharedReqCredsCode() (SharedReqCredsCode, error) {
+ var body SharedReqCredsCode
+ err := json.Unmarshal(t.union, &body)
+ return body, err
+}
+
+// FromSharedReqCredsCode overwrites any union data inside the SharedReqLogin_Creds as the provided SharedReqCredsCode
+func (t *SharedReqLogin_Creds) FromSharedReqCredsCode(v SharedReqCredsCode) error {
+ b, err := json.Marshal(v)
+ t.union = b
+ return err
+}
+
+// MergeSharedReqCredsCode performs a merge with any union data inside the SharedReqLogin_Creds, using the provided SharedReqCredsCode
+func (t *SharedReqLogin_Creds) MergeSharedReqCredsCode(v SharedReqCredsCode) error {
b, err := json.Marshal(v)
if err != nil {
return err
@@ -2257,22 +3069,22 @@ func (t *SharedReqLogin_Creds) MergeSharedReqCredsOIDC(v SharedReqCredsOIDC) err
return err
}
-// AsSharedReqCredsAPIKey returns the union data inside the SharedReqLogin_Creds as a SharedReqCredsAPIKey
-func (t SharedReqLogin_Creds) AsSharedReqCredsAPIKey() (SharedReqCredsAPIKey, error) {
- var body SharedReqCredsAPIKey
+// AsSharedReqCredsPassword returns the union data inside the SharedReqLogin_Creds as a SharedReqCredsPassword
+func (t SharedReqLogin_Creds) AsSharedReqCredsPassword() (SharedReqCredsPassword, error) {
+ var body SharedReqCredsPassword
err := json.Unmarshal(t.union, &body)
return body, err
}
-// FromSharedReqCredsAPIKey overwrites any union data inside the SharedReqLogin_Creds as the provided SharedReqCredsAPIKey
-func (t *SharedReqLogin_Creds) FromSharedReqCredsAPIKey(v SharedReqCredsAPIKey) error {
+// FromSharedReqCredsPassword overwrites any union data inside the SharedReqLogin_Creds as the provided SharedReqCredsPassword
+func (t *SharedReqLogin_Creds) FromSharedReqCredsPassword(v SharedReqCredsPassword) error {
b, err := json.Marshal(v)
t.union = b
return err
}
-// MergeSharedReqCredsAPIKey performs a merge with any union data inside the SharedReqLogin_Creds, using the provided SharedReqCredsAPIKey
-func (t *SharedReqLogin_Creds) MergeSharedReqCredsAPIKey(v SharedReqCredsAPIKey) error {
+// MergeSharedReqCredsPassword performs a merge with any union data inside the SharedReqLogin_Creds, using the provided SharedReqCredsPassword
+func (t *SharedReqLogin_Creds) MergeSharedReqCredsPassword(v SharedReqCredsPassword) error {
b, err := json.Marshal(v)
if err != nil {
return err
@@ -2283,22 +3095,48 @@ func (t *SharedReqLogin_Creds) MergeSharedReqCredsAPIKey(v SharedReqCredsAPIKey)
return err
}
-// AsSharedReqCredsUsername returns the union data inside the SharedReqLogin_Creds as a SharedReqCredsUsername
-func (t SharedReqLogin_Creds) AsSharedReqCredsUsername() (SharedReqCredsUsername, error) {
- var body SharedReqCredsUsername
+// AsSharedReqCredsWebAuthn returns the union data inside the SharedReqLogin_Creds as a SharedReqCredsWebAuthn
+func (t SharedReqLogin_Creds) AsSharedReqCredsWebAuthn() (SharedReqCredsWebAuthn, error) {
+ var body SharedReqCredsWebAuthn
err := json.Unmarshal(t.union, &body)
return body, err
}
-// FromSharedReqCredsUsername overwrites any union data inside the SharedReqLogin_Creds as the provided SharedReqCredsUsername
-func (t *SharedReqLogin_Creds) FromSharedReqCredsUsername(v SharedReqCredsUsername) error {
+// FromSharedReqCredsWebAuthn overwrites any union data inside the SharedReqLogin_Creds as the provided SharedReqCredsWebAuthn
+func (t *SharedReqLogin_Creds) FromSharedReqCredsWebAuthn(v SharedReqCredsWebAuthn) error {
b, err := json.Marshal(v)
t.union = b
return err
}
-// MergeSharedReqCredsUsername performs a merge with any union data inside the SharedReqLogin_Creds, using the provided SharedReqCredsUsername
-func (t *SharedReqLogin_Creds) MergeSharedReqCredsUsername(v SharedReqCredsUsername) error {
+// MergeSharedReqCredsWebAuthn performs a merge with any union data inside the SharedReqLogin_Creds, using the provided SharedReqCredsWebAuthn
+func (t *SharedReqLogin_Creds) MergeSharedReqCredsWebAuthn(v SharedReqCredsWebAuthn) error {
+ b, err := json.Marshal(v)
+ if err != nil {
+ return err
+ }
+
+ merged, err := runtime.JsonMerge(t.union, b)
+ t.union = merged
+ return err
+}
+
+// AsSharedReqCredsNone returns the union data inside the SharedReqLogin_Creds as a SharedReqCredsNone
+func (t SharedReqLogin_Creds) AsSharedReqCredsNone() (SharedReqCredsNone, error) {
+ var body SharedReqCredsNone
+ err := json.Unmarshal(t.union, &body)
+ return body, err
+}
+
+// FromSharedReqCredsNone overwrites any union data inside the SharedReqLogin_Creds as the provided SharedReqCredsNone
+func (t *SharedReqLogin_Creds) FromSharedReqCredsNone(v SharedReqCredsNone) error {
+ b, err := json.Marshal(v)
+ t.union = b
+ return err
+}
+
+// MergeSharedReqCredsNone performs a merge with any union data inside the SharedReqLogin_Creds, using the provided SharedReqCredsNone
+func (t *SharedReqLogin_Creds) MergeSharedReqCredsNone(v SharedReqCredsNone) error {
b, err := json.Marshal(v)
if err != nil {
return err
@@ -2319,22 +3157,22 @@ func (t *SharedReqLogin_Creds) UnmarshalJSON(b []byte) error {
return err
}
-// AsSharedReqParamsEmail returns the union data inside the SharedReqLogin_Params as a SharedReqParamsEmail
-func (t SharedReqLogin_Params) AsSharedReqParamsEmail() (SharedReqParamsEmail, error) {
- var body SharedReqParamsEmail
+// AsSharedReqParamsOIDC returns the union data inside the SharedReqLogin_Params as a SharedReqParamsOIDC
+func (t SharedReqLogin_Params) AsSharedReqParamsOIDC() (SharedReqParamsOIDC, error) {
+ var body SharedReqParamsOIDC
err := json.Unmarshal(t.union, &body)
return body, err
}
-// FromSharedReqParamsEmail overwrites any union data inside the SharedReqLogin_Params as the provided SharedReqParamsEmail
-func (t *SharedReqLogin_Params) FromSharedReqParamsEmail(v SharedReqParamsEmail) error {
+// FromSharedReqParamsOIDC overwrites any union data inside the SharedReqLogin_Params as the provided SharedReqParamsOIDC
+func (t *SharedReqLogin_Params) FromSharedReqParamsOIDC(v SharedReqParamsOIDC) error {
b, err := json.Marshal(v)
t.union = b
return err
}
-// MergeSharedReqParamsEmail performs a merge with any union data inside the SharedReqLogin_Params, using the provided SharedReqParamsEmail
-func (t *SharedReqLogin_Params) MergeSharedReqParamsEmail(v SharedReqParamsEmail) error {
+// MergeSharedReqParamsOIDC performs a merge with any union data inside the SharedReqLogin_Params, using the provided SharedReqParamsOIDC
+func (t *SharedReqLogin_Params) MergeSharedReqParamsOIDC(v SharedReqParamsOIDC) error {
b, err := json.Marshal(v)
if err != nil {
return err
@@ -2345,22 +3183,22 @@ func (t *SharedReqLogin_Params) MergeSharedReqParamsEmail(v SharedReqParamsEmail
return err
}
-// AsSharedReqParamsOIDC returns the union data inside the SharedReqLogin_Params as a SharedReqParamsOIDC
-func (t SharedReqLogin_Params) AsSharedReqParamsOIDC() (SharedReqParamsOIDC, error) {
- var body SharedReqParamsOIDC
+// AsSharedReqParamsPassword returns the union data inside the SharedReqLogin_Params as a SharedReqParamsPassword
+func (t SharedReqLogin_Params) AsSharedReqParamsPassword() (SharedReqParamsPassword, error) {
+ var body SharedReqParamsPassword
err := json.Unmarshal(t.union, &body)
return body, err
}
-// FromSharedReqParamsOIDC overwrites any union data inside the SharedReqLogin_Params as the provided SharedReqParamsOIDC
-func (t *SharedReqLogin_Params) FromSharedReqParamsOIDC(v SharedReqParamsOIDC) error {
+// FromSharedReqParamsPassword overwrites any union data inside the SharedReqLogin_Params as the provided SharedReqParamsPassword
+func (t *SharedReqLogin_Params) FromSharedReqParamsPassword(v SharedReqParamsPassword) error {
b, err := json.Marshal(v)
t.union = b
return err
}
-// MergeSharedReqParamsOIDC performs a merge with any union data inside the SharedReqLogin_Params, using the provided SharedReqParamsOIDC
-func (t *SharedReqLogin_Params) MergeSharedReqParamsOIDC(v SharedReqParamsOIDC) error {
+// MergeSharedReqParamsPassword performs a merge with any union data inside the SharedReqLogin_Params, using the provided SharedReqParamsPassword
+func (t *SharedReqLogin_Params) MergeSharedReqParamsPassword(v SharedReqParamsPassword) error {
b, err := json.Marshal(v)
if err != nil {
return err
@@ -2371,22 +3209,22 @@ func (t *SharedReqLogin_Params) MergeSharedReqParamsOIDC(v SharedReqParamsOIDC)
return err
}
-// AsSharedReqParamsNone returns the union data inside the SharedReqLogin_Params as a SharedReqParamsNone
-func (t SharedReqLogin_Params) AsSharedReqParamsNone() (SharedReqParamsNone, error) {
- var body SharedReqParamsNone
+// AsSharedReqParamsWebAuthn returns the union data inside the SharedReqLogin_Params as a SharedReqParamsWebAuthn
+func (t SharedReqLogin_Params) AsSharedReqParamsWebAuthn() (SharedReqParamsWebAuthn, error) {
+ var body SharedReqParamsWebAuthn
err := json.Unmarshal(t.union, &body)
return body, err
}
-// FromSharedReqParamsNone overwrites any union data inside the SharedReqLogin_Params as the provided SharedReqParamsNone
-func (t *SharedReqLogin_Params) FromSharedReqParamsNone(v SharedReqParamsNone) error {
+// FromSharedReqParamsWebAuthn overwrites any union data inside the SharedReqLogin_Params as the provided SharedReqParamsWebAuthn
+func (t *SharedReqLogin_Params) FromSharedReqParamsWebAuthn(v SharedReqParamsWebAuthn) error {
b, err := json.Marshal(v)
t.union = b
return err
}
-// MergeSharedReqParamsNone performs a merge with any union data inside the SharedReqLogin_Params, using the provided SharedReqParamsNone
-func (t *SharedReqLogin_Params) MergeSharedReqParamsNone(v SharedReqParamsNone) error {
+// MergeSharedReqParamsWebAuthn performs a merge with any union data inside the SharedReqLogin_Params, using the provided SharedReqParamsWebAuthn
+func (t *SharedReqLogin_Params) MergeSharedReqParamsWebAuthn(v SharedReqParamsWebAuthn) error {
b, err := json.Marshal(v)
if err != nil {
return err
@@ -2397,22 +3235,22 @@ func (t *SharedReqLogin_Params) MergeSharedReqParamsNone(v SharedReqParamsNone)
return err
}
-// AsSharedReqParamsUsername returns the union data inside the SharedReqLogin_Params as a SharedReqParamsUsername
-func (t SharedReqLogin_Params) AsSharedReqParamsUsername() (SharedReqParamsUsername, error) {
- var body SharedReqParamsUsername
+// AsSharedReqParamsNone returns the union data inside the SharedReqLogin_Params as a SharedReqParamsNone
+func (t SharedReqLogin_Params) AsSharedReqParamsNone() (SharedReqParamsNone, error) {
+ var body SharedReqParamsNone
err := json.Unmarshal(t.union, &body)
return body, err
}
-// FromSharedReqParamsUsername overwrites any union data inside the SharedReqLogin_Params as the provided SharedReqParamsUsername
-func (t *SharedReqLogin_Params) FromSharedReqParamsUsername(v SharedReqParamsUsername) error {
+// FromSharedReqParamsNone overwrites any union data inside the SharedReqLogin_Params as the provided SharedReqParamsNone
+func (t *SharedReqLogin_Params) FromSharedReqParamsNone(v SharedReqParamsNone) error {
b, err := json.Marshal(v)
t.union = b
return err
}
-// MergeSharedReqParamsUsername performs a merge with any union data inside the SharedReqLogin_Params, using the provided SharedReqParamsUsername
-func (t *SharedReqLogin_Params) MergeSharedReqParamsUsername(v SharedReqParamsUsername) error {
+// MergeSharedReqParamsNone performs a merge with any union data inside the SharedReqLogin_Params, using the provided SharedReqParamsNone
+func (t *SharedReqLogin_Params) MergeSharedReqParamsNone(v SharedReqParamsNone) error {
b, err := json.Marshal(v)
if err != nil {
return err
@@ -2459,22 +3297,22 @@ func (t *SharedResLogin_Params) MergeSharedResParamsOIDC(v SharedResParamsOIDC)
return err
}
-// AsSharedResParamsAPIKey returns the union data inside the SharedResLogin_Params as a SharedResParamsAPIKey
-func (t SharedResLogin_Params) AsSharedResParamsAPIKey() (SharedResParamsAPIKey, error) {
- var body SharedResParamsAPIKey
+// AsSharedResParamsAnonymous returns the union data inside the SharedResLogin_Params as a SharedResParamsAnonymous
+func (t SharedResLogin_Params) AsSharedResParamsAnonymous() (SharedResParamsAnonymous, error) {
+ var body SharedResParamsAnonymous
err := json.Unmarshal(t.union, &body)
return body, err
}
-// FromSharedResParamsAPIKey overwrites any union data inside the SharedResLogin_Params as the provided SharedResParamsAPIKey
-func (t *SharedResLogin_Params) FromSharedResParamsAPIKey(v SharedResParamsAPIKey) error {
+// FromSharedResParamsAnonymous overwrites any union data inside the SharedResLogin_Params as the provided SharedResParamsAnonymous
+func (t *SharedResLogin_Params) FromSharedResParamsAnonymous(v SharedResParamsAnonymous) error {
b, err := json.Marshal(v)
t.union = b
return err
}
-// MergeSharedResParamsAPIKey performs a merge with any union data inside the SharedResLogin_Params, using the provided SharedResParamsAPIKey
-func (t *SharedResLogin_Params) MergeSharedResParamsAPIKey(v SharedResParamsAPIKey) error {
+// MergeSharedResParamsAnonymous performs a merge with any union data inside the SharedResLogin_Params, using the provided SharedResParamsAnonymous
+func (t *SharedResLogin_Params) MergeSharedResParamsAnonymous(v SharedResParamsAnonymous) error {
b, err := json.Marshal(v)
if err != nil {
return err
@@ -2547,22 +3385,22 @@ func (t *SharedResLoginMfa_Params) MergeSharedResParamsOIDC(v SharedResParamsOID
return err
}
-// AsSharedResParamsAPIKey returns the union data inside the SharedResLoginMfa_Params as a SharedResParamsAPIKey
-func (t SharedResLoginMfa_Params) AsSharedResParamsAPIKey() (SharedResParamsAPIKey, error) {
- var body SharedResParamsAPIKey
+// AsSharedResParamsAnonymous returns the union data inside the SharedResLoginMfa_Params as a SharedResParamsAnonymous
+func (t SharedResLoginMfa_Params) AsSharedResParamsAnonymous() (SharedResParamsAnonymous, error) {
+ var body SharedResParamsAnonymous
err := json.Unmarshal(t.union, &body)
return body, err
}
-// FromSharedResParamsAPIKey overwrites any union data inside the SharedResLoginMfa_Params as the provided SharedResParamsAPIKey
-func (t *SharedResLoginMfa_Params) FromSharedResParamsAPIKey(v SharedResParamsAPIKey) error {
+// FromSharedResParamsAnonymous overwrites any union data inside the SharedResLoginMfa_Params as the provided SharedResParamsAnonymous
+func (t *SharedResLoginMfa_Params) FromSharedResParamsAnonymous(v SharedResParamsAnonymous) error {
b, err := json.Marshal(v)
t.union = b
return err
}
-// MergeSharedResParamsAPIKey performs a merge with any union data inside the SharedResLoginMfa_Params, using the provided SharedResParamsAPIKey
-func (t *SharedResLoginMfa_Params) MergeSharedResParamsAPIKey(v SharedResParamsAPIKey) error {
+// MergeSharedResParamsAnonymous performs a merge with any union data inside the SharedResLoginMfa_Params, using the provided SharedResParamsAnonymous
+func (t *SharedResLoginMfa_Params) MergeSharedResParamsAnonymous(v SharedResParamsAnonymous) error {
b, err := json.Marshal(v)
if err != nil {
return err
@@ -2635,22 +3473,22 @@ func (t *SharedResRefresh_Params) MergeSharedResParamsOIDC(v SharedResParamsOIDC
return err
}
-// AsSharedResParamsAPIKey returns the union data inside the SharedResRefresh_Params as a SharedResParamsAPIKey
-func (t SharedResRefresh_Params) AsSharedResParamsAPIKey() (SharedResParamsAPIKey, error) {
- var body SharedResParamsAPIKey
+// AsSharedResParamsAnonymous returns the union data inside the SharedResRefresh_Params as a SharedResParamsAnonymous
+func (t SharedResRefresh_Params) AsSharedResParamsAnonymous() (SharedResParamsAnonymous, error) {
+ var body SharedResParamsAnonymous
err := json.Unmarshal(t.union, &body)
return body, err
}
-// FromSharedResParamsAPIKey overwrites any union data inside the SharedResRefresh_Params as the provided SharedResParamsAPIKey
-func (t *SharedResRefresh_Params) FromSharedResParamsAPIKey(v SharedResParamsAPIKey) error {
+// FromSharedResParamsAnonymous overwrites any union data inside the SharedResRefresh_Params as the provided SharedResParamsAnonymous
+func (t *SharedResRefresh_Params) FromSharedResParamsAnonymous(v SharedResParamsAnonymous) error {
b, err := json.Marshal(v)
t.union = b
return err
}
-// MergeSharedResParamsAPIKey performs a merge with any union data inside the SharedResRefresh_Params, using the provided SharedResParamsAPIKey
-func (t *SharedResRefresh_Params) MergeSharedResParamsAPIKey(v SharedResParamsAPIKey) error {
+// MergeSharedResParamsAnonymous performs a merge with any union data inside the SharedResRefresh_Params, using the provided SharedResParamsAnonymous
+func (t *SharedResRefresh_Params) MergeSharedResParamsAnonymous(v SharedResParamsAnonymous) error {
b, err := json.Marshal(v)
if err != nil {
return err
diff --git a/driver/web/docs/index.yaml b/driver/web/docs/index.yaml
index 161129a5b..946de8ca6 100644
--- a/driver/web/docs/index.yaml
+++ b/driver/web/docs/index.yaml
@@ -39,10 +39,10 @@ paths:
$ref: "./resources/services/auth/login-url.yaml"
/services/auth/logout:
$ref: "./resources/services/auth/logout.yaml"
- /services/auth/credential/verify:
- $ref: "./resources/services/auth/credential/verify.yaml"
- /services/auth/credential/send-verify:
- $ref: "./resources/services/auth/credential/send-verify.yaml"
+ /services/auth/identifier/verify:
+ $ref: "./resources/services/auth/identifier/verify.yaml"
+ /services/auth/identifier/send-verify:
+ $ref: "./resources/services/auth/identifier/send-verify.yaml"
/services/auth/credential/forgot/initiate:
$ref: "./resources/services/auth/credential/forgot/initiate.yaml"
/services/auth/credential/forgot/complete:
@@ -57,6 +57,10 @@ paths:
$ref: "./resources/services/auth/account/can-sign-in.yaml"
/services/auth/account/can-link:
$ref: "./resources/services/auth/account/can-link.yaml"
+ /services/auth/account/sign-in-options:
+ $ref: "./resources/services/auth/account/sign-in-options.yaml"
+ /services/auth/account/identifier/link:
+ $ref: "./resources/services/auth/account/identifier/link.yaml"
/services/auth/account/auth-type/link:
$ref: "./resources/services/auth/account/auth-type/link.yaml"
/services/auth/authorize-service:
@@ -91,6 +95,10 @@ paths:
$ref: "./resources/services/app-configs/configs.yaml"
/services/app-configs/organization:
$ref: "./resources/services/app-configs/organization/configs.yaml"
+
+ # DEPRECATED
+ /services/auth/credential/send-verify:
+ $ref: "./resources/services/auth/credential/send-verify.yaml"
/services/application/configs:
$ref: "./resources/services/application/configs.yaml"
/services/application/organization/configs:
@@ -235,8 +243,8 @@ paths:
#ui
/ui/credential/reset:
$ref: "./resources/ui/credential/reset.yaml"
- /ui/credential/verify:
- $ref: "./resources/ui/credential/verify.yaml"
+ /ui/identifier/verify:
+ $ref: "./resources/ui/identifier/verify.yaml"
#default
/version:
diff --git a/driver/web/docs/resources/services/auth/account/auth-type/link.yaml b/driver/web/docs/resources/services/auth/account/auth-type/link.yaml
index 89b6ed52a..f8bb2294f 100644
--- a/driver/web/docs/resources/services/auth/account/auth-type/link.yaml
+++ b/driver/web/docs/resources/services/auth/account/auth-type/link.yaml
@@ -14,34 +14,43 @@ post:
schema:
$ref: "../../../../../schemas/apis/services/account/auth-type/link/request/Link.yaml"
examples:
- email-sign_up:
- summary: Email
+ password:
+ summary: Password
value:
- auth_type: email
+ auth_type: password
app_type_identifier: edu.illinois.rokwire
- org_id: 0a2eff20-e2cd-11eb-af68-60f81db5ecc0
- api_key: 95a463e3-2ce8-450b-ba75-d8506b874738
creds:
- email: test@example.com
password: test12345
params:
confirm_password: test12345
- phone:
- summary: Phone
+ code:
+ summary: Code
value:
- auth_type: twilio_phone
+ auth_type: code
app_type_identifier: edu.illinois.rokwire
- org_id: 0a2eff20-e2cd-11eb-af68-60f81db5ecc0
- api_key: 95a463e3-2ce8-450b-ba75-d8506b874738
creds:
phone: "+12223334444"
+ webauthn-begin_registration:
+ summary: Webauthn begin registration
+ value:
+ auth_type: webauthn
+ app_type_identifier: edu.illinois.rokwire
+ params:
+ display_name: Name
+ webauthn-complete_registration:
+ summary: Webauthn complete registration
+ value:
+ auth_type: webauthn
+ app_type_identifier: edu.illinois.rokwire
+ creds:
+ response:
+ params:
+ display_name: Name
illinois_oidc:
summary: Illinois OIDC
value:
auth_type: illinois_oidc
app_type_identifier: edu.illinois.rokwire
- org_id: 0a2eff20-e2cd-11eb-af68-60f81db5ecc0
- api_key: 95a463e3-2ce8-450b-ba75-d8506b874738
creds: https://redirect.example.com?code=ai324uith8gSEefesEguorgwsf43
params:
redirect_uri: https://redirect.example.com
@@ -73,13 +82,15 @@ post:
- verification-expired
- already-exists
- not-found
+ - not-allowed
- internal-server-error
description: |
- `invalid`: Invalid credentials
- `unverified`: Unverified credentials
- - `verification-expired`: Credentials verification expired. The verification is restarted
- - `already-exists`: Auth type identifier already exists
+ - `verification-expired`: Identifier verification expired. The verification is restarted
+ - `already-exists`: Auth type already exists
- `not-found`: Account could not be found when `sign-up=false`
+ - `not-allowed`: Invalid operation
- `internal-server-error`: An undefined error occurred
message:
type: string
@@ -98,25 +109,8 @@ delete:
application/json:
schema:
$ref: "../../../../../schemas/apis/services/account/auth-type/link/request/Unlink.yaml"
- examples:
- email:
- summary: Email
- value:
- auth_type: email
- app_type_identifier: edu.illinois.rokwire
- identifier: test@example.com
- phone:
- summary: Phone
- value:
- auth_type: twilio_phone
- app_type_identifier: edu.illinois.rokwire
- identifier: "+12223334444"
- illinois_oidc:
- summary: Illinois OIDC
- value:
- auth_type: illinois_oidc
- app_type_identifier: edu.illinois.rokwire
- identifier: "123456789"
+ example:
+ id:
required: true
responses:
200:
diff --git a/driver/web/docs/resources/services/auth/account/identifier/link.yaml b/driver/web/docs/resources/services/auth/account/identifier/link.yaml
new file mode 100644
index 000000000..9d86222fe
--- /dev/null
+++ b/driver/web/docs/resources/services/auth/account/identifier/link.yaml
@@ -0,0 +1,101 @@
+post:
+ tags:
+ - Services
+ summary: Link identifier
+ description: |
+ Link identifier to an existing account
+
+ **Auth:** Requires "authenticated" auth token
+ security:
+ - bearerAuth: []
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "../../../../../schemas/apis/services/account/identifier/link/request/Link.yaml"
+ examples:
+ email:
+ summary: Email
+ value:
+ identifier:
+ email: test@example.com
+ phone:
+ summary: Phone
+ value:
+ identifier:
+ phone: "+12223334444"
+ username:
+ summary: Username
+ value:
+ identifier:
+ username: username
+ required: true
+ responses:
+ 200:
+ description: Success
+ content:
+ application/json:
+ schema:
+ $ref: "../../../../../schemas/apis/services/account/identifier/link/response/Response.yaml"
+ 400:
+ description: Bad request
+ 401:
+ description: Unauthorized
+ 500:
+ description: Internal error
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ enum:
+ - invalid
+ - unverified
+ - verification-expired
+ - already-exists
+ - not-found
+ - not-allowed
+ - internal-server-error
+ description: |
+ - `invalid`: Invalid identifier
+ - `unverified`: Unverified identifier
+ - `verification-expired`: Identifier verification expired. The verification is restarted
+ - `already-exists`: Auth type identifier already exists
+ - `not-found`: Account could not be found when `sign-up=false`
+ - `not-allowed`: Invalid operation
+ - `internal-server-error`: An undefined error occurred
+ message:
+ type: string
+delete:
+ tags:
+ - Services
+ summary: Unlink identifier
+ description: |
+ Unlink identifier from an existing account
+
+ **Auth:** Requires "authenticated" auth token
+ security:
+ - bearerAuth: []
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "../../../../../schemas/apis/services/account/identifier/link/request/Unlink.yaml"
+ example:
+ id:
+ required: true
+ responses:
+ 200:
+ description: Success
+ content:
+ application/json:
+ schema:
+ $ref: "../../../../../schemas/apis/services/account/identifier/link/response/Response.yaml"
+ 400:
+ description: Bad request
+ 401:
+ description: Unauthorized
+ 500:
+ description: Internal error
\ No newline at end of file
diff --git a/driver/web/docs/resources/services/auth/account/sign-in-options.yaml b/driver/web/docs/resources/services/auth/account/sign-in-options.yaml
new file mode 100644
index 000000000..4b8f6d390
--- /dev/null
+++ b/driver/web/docs/resources/services/auth/account/sign-in-options.yaml
@@ -0,0 +1,27 @@
+post:
+ tags:
+ - Services
+ summary: Get account sign-in options
+ description: |
+ Get the sign-in options for the account with the provided parameters
+ requestBody:
+ description: |
+ Account information to be checked
+ content:
+ application/json:
+ schema:
+ $ref: "../../../../schemas/apis/shared/requests/AccountCheck.yaml"
+ required: true
+ responses:
+ 200:
+ description: Success
+ content:
+ application/json:
+ schema:
+ $ref: "../../../../schemas/apis/shared/responses/SignInOptions.yaml"
+ 400:
+ description: Bad request
+ 401:
+ description: Unauthorized
+ 500:
+ description: Internal error
\ No newline at end of file
diff --git a/driver/web/docs/resources/services/auth/credential/send-verify.yaml b/driver/web/docs/resources/services/auth/credential/send-verify.yaml
index b66d223b1..b4ff76da3 100644
--- a/driver/web/docs/resources/services/auth/credential/send-verify.yaml
+++ b/driver/web/docs/resources/services/auth/credential/send-verify.yaml
@@ -4,13 +4,14 @@ post:
summary: Send verification code to identifier
description: |
Sends verification code to identifier to verify account ownership
+ deprecated: true
requestBody:
description: |
Account information to be checked
content:
application/json:
schema:
- $ref: "../../../../schemas/apis/services/credential/send-verify/request/Request.yaml"
+ $ref: "../../../../schemas/apis/services/identifier/send-verify/request/Request.yaml"
required: true
responses:
200:
diff --git a/driver/web/docs/resources/services/auth/identifier/send-verify.yaml b/driver/web/docs/resources/services/auth/identifier/send-verify.yaml
new file mode 100644
index 000000000..d5196cd1d
--- /dev/null
+++ b/driver/web/docs/resources/services/auth/identifier/send-verify.yaml
@@ -0,0 +1,28 @@
+post:
+ tags:
+ - Services
+ summary: Send verification code to identifier
+ description: |
+ Sends verification code to identifier to verify account ownership
+ requestBody:
+ description: |
+ Account information to be checked
+ content:
+ application/json:
+ schema:
+ $ref: "../../../../schemas/apis/services/identifier/send-verify/request/Request.yaml"
+ required: true
+ responses:
+ 200:
+ description: Successful operation
+ content:
+ text/plain:
+ schema:
+ type: string
+ example: Successfully sent verification code
+ 400:
+ description: Bad request
+ 401:
+ description: Unauthorized
+ 500:
+ description: Internal error
diff --git a/driver/web/docs/resources/services/auth/credential/verify.yaml b/driver/web/docs/resources/services/auth/identifier/verify.yaml
similarity index 86%
rename from driver/web/docs/resources/services/auth/credential/verify.yaml
rename to driver/web/docs/resources/services/auth/identifier/verify.yaml
index 5e8c57ae8..e983059de 100644
--- a/driver/web/docs/resources/services/auth/credential/verify.yaml
+++ b/driver/web/docs/resources/services/auth/identifier/verify.yaml
@@ -3,11 +3,11 @@ get:
- Services
summary: Validate verification code
description: |
- Validates verification code to verify account ownership
+ Validates verification code to verify account identifier ownership
parameters:
- name: id
in: query
- description: Credential ID
+ description: Account identifier ID
required: true
style: form
explode: false
diff --git a/driver/web/docs/resources/services/auth/login.yaml b/driver/web/docs/resources/services/auth/login.yaml
index 89ac0551a..e3fcb1249 100644
--- a/driver/web/docs/resources/services/auth/login.yaml
+++ b/driver/web/docs/resources/services/auth/login.yaml
@@ -180,6 +180,35 @@ post:
type: mobile
device_id: "5555"
os: Android
+ webauthn-sign_up:
+ summary: WebAuthn - sign up
+ value:
+ auth_type: webauthn
+ app_type_identifier: edu.illinois.rokwire
+ org_id: 0a2eff20-e2cd-11eb-af68-60f81db5ecc0
+ api_key: 95a463e3-2ce8-450b-ba75-d8506b874738
+ params:
+ sign_up: true
+ name: test
+ display_name: John Doe
+ preferences:
+ key1: value1
+ key2: value2
+ profile:
+ address: address
+ birth_year: 1990
+ country: county
+ email: email
+ first_name: first name
+ last_name: last name
+ phone: "+000000000000"
+ photo_url: photo url
+ state: state
+ zip_code: zip code
+ device:
+ type: mobile
+ device_id: "5555"
+ os: Android
required: true
responses:
200:
diff --git a/driver/web/docs/resources/ui/credential/verify.yaml b/driver/web/docs/resources/ui/identifier/verify.yaml
similarity index 95%
rename from driver/web/docs/resources/ui/credential/verify.yaml
rename to driver/web/docs/resources/ui/identifier/verify.yaml
index f1ed9369b..555739309 100644
--- a/driver/web/docs/resources/ui/credential/verify.yaml
+++ b/driver/web/docs/resources/ui/identifier/verify.yaml
@@ -7,7 +7,7 @@ get:
parameters:
- name: id
in: query
- description: Credential ID
+ description: Identifier ID
required: true
style: form
explode: false
diff --git a/driver/web/docs/schemas/apis/admin/configs/request/Request.yaml b/driver/web/docs/schemas/apis/admin/configs/request/Request.yaml
index b356a62f5..d66f4c2f1 100644
--- a/driver/web/docs/schemas/apis/admin/configs/request/Request.yaml
+++ b/driver/web/docs/schemas/apis/admin/configs/request/Request.yaml
@@ -16,4 +16,5 @@ properties:
type: boolean
data:
anyOf:
- - $ref: "../../../../config/EnvConfigData.yaml"
\ No newline at end of file
+ - $ref: "../../../../config/EnvConfigData.yaml"
+ - $ref: "../../../../config/AuthConfigData.yaml"
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/services/account/auth-type/link/request/Link.yaml b/driver/web/docs/schemas/apis/services/account/auth-type/link/request/Link.yaml
index 5f5223022..9acd94d09 100644
--- a/driver/web/docs/schemas/apis/services/account/auth-type/link/request/Link.yaml
+++ b/driver/web/docs/schemas/apis/services/account/auth-type/link/request/Link.yaml
@@ -1,26 +1,33 @@
required:
- auth_type
- app_type_identifier
- - creds
type: object
properties:
auth_type:
type: string
enum:
+ - password
+ - webauthn
+ - code
+ - illinois_oidc
+ - conde_oidc
- email
+ - phone
- twilio_phone
- - illinois_oidc
- username
app_type_identifier:
type: string
creds:
anyOf:
- - $ref: "../../../../../shared/requests/CredsEmail.yaml"
- - $ref: "../../../../../shared/requests/CredsTwilioPhone.yaml"
+ - $ref: "../../../../../shared/requests/CredsCode.yaml"
- $ref: "../../../../../shared/requests/CredsOIDC.yaml"
+ - $ref: "../../../../../shared/requests/CredsPassword.yaml"
+ - $ref: "../../../../../shared/requests/CredsWebAuthn.yaml"
+ - $ref: "../../../../../shared/requests/CredsNone.yaml"
params:
type: object
anyOf:
- - $ref: "../../../../../shared/requests/ParamsEmail.yaml"
- $ref: "../../../../../shared/requests/ParamsOIDC.yaml"
+ - $ref: "../../../../../shared/requests/ParamsPassword.yaml"
+ - $ref: "../../../../../shared/requests/ParamsWebAuthn.yaml"
- $ref: "../../../../../shared/requests/ParamsNone.yaml"
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/services/account/auth-type/link/request/Unlink.yaml b/driver/web/docs/schemas/apis/services/account/auth-type/link/request/Unlink.yaml
index bf5d3f672..285d4307f 100644
--- a/driver/web/docs/schemas/apis/services/account/auth-type/link/request/Unlink.yaml
+++ b/driver/web/docs/schemas/apis/services/account/auth-type/link/request/Unlink.yaml
@@ -1,17 +1,18 @@
-required:
- - auth_type
- - app_type_identifier
- - identifier
type: object
properties:
+ id:
+ type: string
auth_type:
type: string
enum:
+ - password
+ - webauthn
+ - code
+ - illinois_oidc
+ - conde_oidc
- email
+ - phone
- twilio_phone
- - illinois_oidc
- username
- app_type_identifier:
- type: string
identifier:
type: string
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/services/account/auth-type/link/response/Response.yaml b/driver/web/docs/schemas/apis/services/account/auth-type/link/response/Response.yaml
index 15da85bde..780a36278 100644
--- a/driver/web/docs/schemas/apis/services/account/auth-type/link/response/Response.yaml
+++ b/driver/web/docs/schemas/apis/services/account/auth-type/link/response/Response.yaml
@@ -5,6 +5,10 @@ properties:
message:
type: string
nullable: true
+ identifiers:
+ type: array
+ items:
+ $ref: "../../../../../../user/AccountIdentifier.yaml"
auth_types:
type: array
items:
diff --git a/driver/web/docs/schemas/apis/services/account/identifier/link/request/Link.yaml b/driver/web/docs/schemas/apis/services/account/identifier/link/request/Link.yaml
new file mode 100644
index 000000000..20f23b97c
--- /dev/null
+++ b/driver/web/docs/schemas/apis/services/account/identifier/link/request/Link.yaml
@@ -0,0 +1,6 @@
+required:
+ - identifier
+type: object
+properties:
+ identifier:
+ $ref: "../../../../../shared/requests/Identifiers.yaml"
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/services/account/identifier/link/request/Unlink.yaml b/driver/web/docs/schemas/apis/services/account/identifier/link/request/Unlink.yaml
new file mode 100644
index 000000000..cf30202d3
--- /dev/null
+++ b/driver/web/docs/schemas/apis/services/account/identifier/link/request/Unlink.yaml
@@ -0,0 +1,6 @@
+required:
+ - id
+type: object
+properties:
+ id:
+ type: string
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/services/account/identifier/link/response/Response.yaml b/driver/web/docs/schemas/apis/services/account/identifier/link/response/Response.yaml
new file mode 100644
index 000000000..e6028878d
--- /dev/null
+++ b/driver/web/docs/schemas/apis/services/account/identifier/link/response/Response.yaml
@@ -0,0 +1,11 @@
+required:
+ - identifiers
+type: object
+properties:
+ message:
+ type: string
+ nullable: true
+ identifiers:
+ type: array
+ items:
+ $ref: "../../../../../../user/AccountIdentifier.yaml"
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/services/credential/forgot/complete/request/Request.yaml b/driver/web/docs/schemas/apis/services/credential/forgot/complete/request/Request.yaml
index 6adb19c92..58abfef70 100644
--- a/driver/web/docs/schemas/apis/services/credential/forgot/complete/request/Request.yaml
+++ b/driver/web/docs/schemas/apis/services/credential/forgot/complete/request/Request.yaml
@@ -10,4 +10,4 @@ properties:
params:
type: object
anyOf:
- - $ref: "../../../../../shared/requests/ParamsSetEmailCredential.yaml"
\ No newline at end of file
+ - $ref: "../../../../../shared/requests/ParamsResetPassword.yaml"
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/services/credential/forgot/initiate/request/Request.yaml b/driver/web/docs/schemas/apis/services/credential/forgot/initiate/request/Request.yaml
index d91f8df2e..d50c15154 100644
--- a/driver/web/docs/schemas/apis/services/credential/forgot/initiate/request/Request.yaml
+++ b/driver/web/docs/schemas/apis/services/credential/forgot/initiate/request/Request.yaml
@@ -1,20 +1,23 @@
required:
- auth_type
+ - identifier
- app_type_identifier
- org_id
- api_key
- - identifier
type: object
properties:
auth_type:
type: string
enum:
+ - password
- email
+ identifier:
+ oneOf:
+ - $ref: "../../../../../shared/requests/Identifiers.yaml"
+ - $ref: "../../../../../shared/requests/IdentifierString.yaml"
app_type_identifier:
type: string
org_id:
type: string
api_key:
- type: string
- identifier:
type: string
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/services/credential/update/request/Request.yaml b/driver/web/docs/schemas/apis/services/credential/update/request/Request.yaml
index 3fa77dc87..4bd3f17fe 100644
--- a/driver/web/docs/schemas/apis/services/credential/update/request/Request.yaml
+++ b/driver/web/docs/schemas/apis/services/credential/update/request/Request.yaml
@@ -7,4 +7,4 @@ properties:
params:
type: object
anyOf:
- - $ref: "../../../../shared/requests/ParamsSetEmailCredential.yaml"
\ No newline at end of file
+ - $ref: "../../../../shared/requests/ParamsResetPassword.yaml"
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/services/credential/send-verify/request/Request.yaml b/driver/web/docs/schemas/apis/services/identifier/send-verify/request/Request.yaml
similarity index 65%
rename from driver/web/docs/schemas/apis/services/credential/send-verify/request/Request.yaml
rename to driver/web/docs/schemas/apis/services/identifier/send-verify/request/Request.yaml
index 11e76f7c5..3b55b2c6b 100644
--- a/driver/web/docs/schemas/apis/services/credential/send-verify/request/Request.yaml
+++ b/driver/web/docs/schemas/apis/services/identifier/send-verify/request/Request.yaml
@@ -1,5 +1,4 @@
required:
- - auth_type
- app_type_identifier
- org_id
- api_key
@@ -7,7 +6,9 @@ required:
type: object
properties:
identifier:
- type: string
+ oneOf:
+ - $ref: "../../../../shared/requests/Identifiers.yaml"
+ - $ref: "../../../../shared/requests/IdentifierString.yaml"
org_id:
type: string
api_key:
diff --git a/driver/web/docs/schemas/apis/shared/requests/AccountCheck.yaml b/driver/web/docs/schemas/apis/shared/requests/AccountCheck.yaml
index 7e6f15c8d..0c9ceb024 100644
--- a/driver/web/docs/schemas/apis/shared/requests/AccountCheck.yaml
+++ b/driver/web/docs/schemas/apis/shared/requests/AccountCheck.yaml
@@ -1,24 +1,29 @@
required:
- - auth_type
- app_type_identifier
- org_id
- api_key
- - user_identifier
type: object
properties:
+ app_type_identifier:
+ type: string
+ org_id:
+ type: string
+ api_key:
+ type: string
+ identifier:
+ $ref: "./Identifiers.yaml"
auth_type:
type: string
enum:
- username
- email
+ - phone
+ - anonymous
- twilio_phone
- illinois_oidc
- - anonymous
- app_type_identifier:
- type: string
- org_id:
- type: string
- api_key:
- type: string
+ - conde_oidc
+ deprecated: true
user_identifier:
- type: string
\ No newline at end of file
+ type: string
+ deprecated: true
+
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/shared/requests/CredsAPIKey.yaml b/driver/web/docs/schemas/apis/shared/requests/CredsAnonymous.yaml
similarity index 100%
rename from driver/web/docs/schemas/apis/shared/requests/CredsAPIKey.yaml
rename to driver/web/docs/schemas/apis/shared/requests/CredsAnonymous.yaml
diff --git a/driver/web/docs/schemas/apis/shared/requests/CredsCode.yaml b/driver/web/docs/schemas/apis/shared/requests/CredsCode.yaml
new file mode 100644
index 000000000..b66b9f5ed
--- /dev/null
+++ b/driver/web/docs/schemas/apis/shared/requests/CredsCode.yaml
@@ -0,0 +1,7 @@
+type: object
+description: Auth login creds for auth_type="code"
+allOf:
+ - $ref: "./Identifiers.yaml"
+ - properties:
+ code:
+ type: string
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/shared/requests/CredsEmail.yaml b/driver/web/docs/schemas/apis/shared/requests/CredsEmail.yaml
deleted file mode 100644
index a33facbd3..000000000
--- a/driver/web/docs/schemas/apis/shared/requests/CredsEmail.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-required:
- - email
- - password
-type: object
-description: Auth login creds for auth_type="email"
-properties:
- email:
- type: string
- password:
- type: string
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/shared/requests/CredsNone.yaml b/driver/web/docs/schemas/apis/shared/requests/CredsNone.yaml
new file mode 100644
index 000000000..2ceb741a6
--- /dev/null
+++ b/driver/web/docs/schemas/apis/shared/requests/CredsNone.yaml
@@ -0,0 +1,3 @@
+type: object
+description: Auth login request creds for unlisted auth_types (None)
+nullable: true
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/shared/requests/CredsPassword.yaml b/driver/web/docs/schemas/apis/shared/requests/CredsPassword.yaml
new file mode 100644
index 000000000..edd5b7f2f
--- /dev/null
+++ b/driver/web/docs/schemas/apis/shared/requests/CredsPassword.yaml
@@ -0,0 +1,9 @@
+type: object
+description: Auth login creds for auth_type="password"
+allOf:
+ - $ref: "./Identifiers.yaml"
+ - required:
+ - password
+ properties:
+ password:
+ type: string
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/shared/requests/CredsTwilioPhone.yaml b/driver/web/docs/schemas/apis/shared/requests/CredsTwilioPhone.yaml
deleted file mode 100644
index 091da0bb7..000000000
--- a/driver/web/docs/schemas/apis/shared/requests/CredsTwilioPhone.yaml
+++ /dev/null
@@ -1,9 +0,0 @@
-type: object
-description: Auth login creds for auth_type="twilio_phone"
-required:
- - phone
-properties:
- phone:
- type: string
- code:
- type: string
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/shared/requests/CredsUsername.yaml b/driver/web/docs/schemas/apis/shared/requests/CredsUsername.yaml
deleted file mode 100644
index 8188a37e4..000000000
--- a/driver/web/docs/schemas/apis/shared/requests/CredsUsername.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-required:
- - username
- - password
-type: object
-description: Auth login creds for auth_type="username"
-properties:
- username:
- type: string
- password:
- type: string
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/shared/requests/CredsWebAuthn.yaml b/driver/web/docs/schemas/apis/shared/requests/CredsWebAuthn.yaml
new file mode 100644
index 000000000..9a66d8ac6
--- /dev/null
+++ b/driver/web/docs/schemas/apis/shared/requests/CredsWebAuthn.yaml
@@ -0,0 +1,7 @@
+type: object
+description: Auth login creds for auth_type="webauthn"
+allOf:
+ - $ref: "./Identifiers.yaml"
+ - properties:
+ response:
+ type: string
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/shared/requests/IdentifierString.yaml b/driver/web/docs/schemas/apis/shared/requests/IdentifierString.yaml
new file mode 100644
index 000000000..23d3801f8
--- /dev/null
+++ b/driver/web/docs/schemas/apis/shared/requests/IdentifierString.yaml
@@ -0,0 +1,2 @@
+type: string
+description: User identifier string
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/shared/requests/Identifiers.yaml b/driver/web/docs/schemas/apis/shared/requests/Identifiers.yaml
new file mode 100644
index 000000000..65f7f322d
--- /dev/null
+++ b/driver/web/docs/schemas/apis/shared/requests/Identifiers.yaml
@@ -0,0 +1,11 @@
+type: object
+description: Allowed identifier types
+properties:
+ username:
+ type: string
+ email:
+ type: string
+ phone:
+ type: string
+additionalProperties:
+ type: string
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/shared/requests/ParamsEmail.yaml b/driver/web/docs/schemas/apis/shared/requests/ParamsPassword.yaml
similarity index 100%
rename from driver/web/docs/schemas/apis/shared/requests/ParamsEmail.yaml
rename to driver/web/docs/schemas/apis/shared/requests/ParamsPassword.yaml
diff --git a/driver/web/docs/schemas/apis/shared/requests/ParamsSetEmailCredential.yaml b/driver/web/docs/schemas/apis/shared/requests/ParamsResetPassword.yaml
similarity index 100%
rename from driver/web/docs/schemas/apis/shared/requests/ParamsSetEmailCredential.yaml
rename to driver/web/docs/schemas/apis/shared/requests/ParamsResetPassword.yaml
diff --git a/driver/web/docs/schemas/apis/shared/requests/ParamsUsername.yaml b/driver/web/docs/schemas/apis/shared/requests/ParamsUsername.yaml
deleted file mode 100644
index 9f070b9b4..000000000
--- a/driver/web/docs/schemas/apis/shared/requests/ParamsUsername.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-type: object
-description: Auth login params for auth_type="username"
-properties:
- confirm_password:
- type: string
- description: This should match the `creds` password field when sign_up=true. This should be verified on the client side as well to reduce invalid requests.
- sign_up:
- type: boolean
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/shared/requests/ParamsWebAuthn.yaml b/driver/web/docs/schemas/apis/shared/requests/ParamsWebAuthn.yaml
new file mode 100644
index 000000000..00de65a04
--- /dev/null
+++ b/driver/web/docs/schemas/apis/shared/requests/ParamsWebAuthn.yaml
@@ -0,0 +1,8 @@
+type: object
+description: Auth login params for auth_type="webauthn"
+properties:
+ display_name:
+ type: string
+ description: User's account name for display purposes
+ sign_up:
+ type: boolean
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/shared/requests/create-account/Request.yaml b/driver/web/docs/schemas/apis/shared/requests/create-account/Request.yaml
index 81025edbb..113ec3b00 100644
--- a/driver/web/docs/schemas/apis/shared/requests/create-account/Request.yaml
+++ b/driver/web/docs/schemas/apis/shared/requests/create-account/Request.yaml
@@ -6,10 +6,10 @@ properties:
auth_type:
type: string
enum:
- - email
+ - password
- illinois_oidc
identifier:
- type: string
+ $ref: "../Identifiers.yaml"
permissions:
type: array
items:
@@ -29,7 +29,4 @@ properties:
profile:
$ref: "../../../../user/ProfileNullable.yaml"
privacy:
- $ref: "../../../../user/PrivacyNullable.yaml"
- username:
- type: string
- nullable: true
\ No newline at end of file
+ $ref: "../../../../user/PrivacyNullable.yaml"
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/shared/requests/login-url/Request.yaml b/driver/web/docs/schemas/apis/shared/requests/login-url/Request.yaml
index 3c2c5a819..760f9959e 100644
--- a/driver/web/docs/schemas/apis/shared/requests/login-url/Request.yaml
+++ b/driver/web/docs/schemas/apis/shared/requests/login-url/Request.yaml
@@ -10,6 +10,7 @@ properties:
type: string
enum:
- illinois_oidc
+ - conde_oidc
app_type_identifier:
type: string
org_id:
diff --git a/driver/web/docs/schemas/apis/shared/requests/login/Request.yaml b/driver/web/docs/schemas/apis/shared/requests/login/Request.yaml
index ff2dc606e..634d8816f 100644
--- a/driver/web/docs/schemas/apis/shared/requests/login/Request.yaml
+++ b/driver/web/docs/schemas/apis/shared/requests/login/Request.yaml
@@ -10,10 +10,15 @@ properties:
type: string
enum:
- email
+ - phone
- twilio_phone
- illinois_oidc
+ - conde_oidc
- anonymous
- username
+ - password
+ - webauthn
+ - code
app_type_identifier:
type: string
org_id:
@@ -22,18 +27,19 @@ properties:
type: string
creds:
anyOf:
- - $ref: "../CredsEmail.yaml"
- - $ref: "../CredsTwilioPhone.yaml"
+ - $ref: "../CredsAnonymous.yaml"
+ - $ref: "../CredsCode.yaml"
- $ref: "../CredsOIDC.yaml"
- - $ref: "../CredsAPIKey.yaml"
- - $ref: "../CredsUsername.yaml"
+ - $ref: "../CredsPassword.yaml"
+ - $ref: "../CredsWebAuthn.yaml"
+ - $ref: "../CredsNone.yaml"
params:
type: object
anyOf:
- - $ref: "../ParamsEmail.yaml"
- $ref: "../ParamsOIDC.yaml"
+ - $ref: "../ParamsPassword.yaml"
+ - $ref: "../ParamsWebAuthn.yaml"
- $ref: "../ParamsNone.yaml"
- - $ref: "../ParamsUsername.yaml"
device:
$ref: "../../../../user/Device.yaml"
profile:
@@ -43,6 +49,6 @@ properties:
preferences:
type: object
nullable: true
- username:
+ account_identifier_id:
type: string
- nullable: true
+ nullable: true
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/shared/requests/update-account/Request.yaml b/driver/web/docs/schemas/apis/shared/requests/update-account/Request.yaml
index 746bdf38f..3e7f15bbf 100644
--- a/driver/web/docs/schemas/apis/shared/requests/update-account/Request.yaml
+++ b/driver/web/docs/schemas/apis/shared/requests/update-account/Request.yaml
@@ -6,10 +6,10 @@ properties:
auth_type:
type: string
enum:
- - email
+ - password
- illinois_oidc
identifier:
- type: string
+ $ref: "../Identifiers.yaml"
permissions:
type: array
items:
diff --git a/driver/web/docs/schemas/apis/shared/responses/ParamsAPIKey.yaml b/driver/web/docs/schemas/apis/shared/responses/ParamsAnonymous.yaml
similarity index 100%
rename from driver/web/docs/schemas/apis/shared/responses/ParamsAPIKey.yaml
rename to driver/web/docs/schemas/apis/shared/responses/ParamsAnonymous.yaml
diff --git a/driver/web/docs/schemas/apis/shared/responses/SignInOptions.yaml b/driver/web/docs/schemas/apis/shared/responses/SignInOptions.yaml
new file mode 100644
index 000000000..e333e7c01
--- /dev/null
+++ b/driver/web/docs/schemas/apis/shared/responses/SignInOptions.yaml
@@ -0,0 +1,13 @@
+required:
+ - identifiers
+ - auth_types
+type: object
+properties:
+ identifiers:
+ type: array
+ items:
+ $ref: "../../../user/AccountIdentifier.yaml"
+ auth_types:
+ type: array
+ items:
+ $ref: "../../../user/AccountAuthType.yaml"
diff --git a/driver/web/docs/schemas/apis/shared/responses/login/MfaResponse.yaml b/driver/web/docs/schemas/apis/shared/responses/login/MfaResponse.yaml
index 1f154e255..0e5928c64 100644
--- a/driver/web/docs/schemas/apis/shared/responses/login/MfaResponse.yaml
+++ b/driver/web/docs/schemas/apis/shared/responses/login/MfaResponse.yaml
@@ -20,5 +20,5 @@ properties:
nullable: true
anyOf:
- $ref: "../ParamsOIDC.yaml"
- - $ref: "../ParamsAPIKey.yaml"
+ - $ref: "../ParamsAnonymous.yaml"
- $ref: "../ParamsNone.yaml"
\ No newline at end of file
diff --git a/driver/web/docs/schemas/apis/shared/responses/login/Response.yaml b/driver/web/docs/schemas/apis/shared/responses/login/Response.yaml
index f2e9189be..3823b635e 100644
--- a/driver/web/docs/schemas/apis/shared/responses/login/Response.yaml
+++ b/driver/web/docs/schemas/apis/shared/responses/login/Response.yaml
@@ -9,7 +9,7 @@ properties:
nullable: true
anyOf:
- $ref: "../ParamsOIDC.yaml"
- - $ref: "../ParamsAPIKey.yaml"
+ - $ref: "../ParamsAnonymous.yaml"
- $ref: "../ParamsNone.yaml"
message:
type: string
diff --git a/driver/web/docs/schemas/apis/shared/responses/refresh/Response.yaml b/driver/web/docs/schemas/apis/shared/responses/refresh/Response.yaml
index ce27aaf49..39bdb9871 100644
--- a/driver/web/docs/schemas/apis/shared/responses/refresh/Response.yaml
+++ b/driver/web/docs/schemas/apis/shared/responses/refresh/Response.yaml
@@ -7,5 +7,5 @@ properties:
nullable: true
anyOf:
- $ref: "../ParamsOIDC.yaml"
- - $ref: "../ParamsAPIKey.yaml"
+ - $ref: "../ParamsAnonymous.yaml"
- $ref: "../ParamsNone.yaml"
diff --git a/driver/web/docs/schemas/application/IdentityProviderSettings.yaml b/driver/web/docs/schemas/application/IdentityProviderSettings.yaml
index 1943b5a47..e1f404d8d 100644
--- a/driver/web/docs/schemas/application/IdentityProviderSettings.yaml
+++ b/driver/web/docs/schemas/application/IdentityProviderSettings.yaml
@@ -7,11 +7,18 @@ properties:
type: string
user_identifier_field:
type: string
- external_id_fields: # map
+ external_id_fields:
type: object
additionalProperties:
type: string
nullable: true
+ sensitive_external_ids:
+ type: array
+ items:
+ type: string
+ nullable: true
+ is_email_verified:
+ type: boolean
first_name_field:
type: string
middle_name_field:
diff --git a/driver/web/docs/schemas/auth/AuthType.yaml b/driver/web/docs/schemas/auth/AuthType.yaml
index b20f18c51..bc0ea8112 100644
--- a/driver/web/docs/schemas/auth/AuthType.yaml
+++ b/driver/web/docs/schemas/auth/AuthType.yaml
@@ -13,7 +13,7 @@ properties:
type: string
code:
type: string
- description: "username or email or phone or illinois_oidc etc"
+ description: "passowrd or code or webauthn or illinois_oidc etc"
description:
type: string
is_external:
diff --git a/driver/web/docs/schemas/auth/LoginSession.yaml b/driver/web/docs/schemas/auth/LoginSession.yaml
index 980b86aa2..b8def46c5 100644
--- a/driver/web/docs/schemas/auth/LoginSession.yaml
+++ b/driver/web/docs/schemas/auth/LoginSession.yaml
@@ -14,10 +14,6 @@ properties:
type: string
app_type_identifier:
type: string
- account_auth_type_id:
- type: string
- account_auth_type_identifier:
- type: string
device_id:
type: string
nullable: true
diff --git a/driver/web/docs/schemas/config/AuthConfigData.yaml b/driver/web/docs/schemas/config/AuthConfigData.yaml
new file mode 100644
index 000000000..fc746a8b5
--- /dev/null
+++ b/driver/web/docs/schemas/config/AuthConfigData.yaml
@@ -0,0 +1,11 @@
+type: object
+properties:
+ email_should_verify:
+ type: boolean
+ nullable: true
+ email_verify_wait_time:
+ type: integer
+ nullable: true
+ email_verify_expiry:
+ type: integer
+ nullable: true
\ No newline at end of file
diff --git a/driver/web/docs/schemas/config/Config.yaml b/driver/web/docs/schemas/config/Config.yaml
index e69bb7109..a0a853850 100644
--- a/driver/web/docs/schemas/config/Config.yaml
+++ b/driver/web/docs/schemas/config/Config.yaml
@@ -25,6 +25,7 @@ properties:
data:
anyOf:
- $ref: "./EnvConfigData.yaml"
+ - $ref: "./AuthConfigData.yaml"
date_created:
readOnly: true
type: string
diff --git a/driver/web/docs/schemas/index.yaml b/driver/web/docs/schemas/index.yaml
index 3d15d0165..7fca1adf6 100644
--- a/driver/web/docs/schemas/index.yaml
+++ b/driver/web/docs/schemas/index.yaml
@@ -3,6 +3,8 @@ Config:
$ref: "./config/Config.yaml"
EnvConfigData:
$ref: "./config/EnvConfigData.yaml"
+AuthConfigData:
+ $ref: "./config/AuthConfigData.yaml"
# application
Application:
@@ -94,6 +96,8 @@ Username:
$ref: "./user/Username.yaml"
AccountAuthType:
$ref: "./user/AccountAuthType.yaml"
+AccountIdentifier:
+ $ref: "./user/AccountIdentifier.yaml"
Device:
$ref: "./user/Device.yaml"
Follow:
@@ -120,26 +124,32 @@ _shared_req_UpdateAccount:
$ref: "./apis/shared/requests/update-account/Request.yaml"
_shared_req_AccountCheck:
$ref: "./apis/shared/requests/AccountCheck.yaml"
-_shared_req_CredsEmail:
- $ref: "./apis/shared/requests/CredsEmail.yaml"
-_shared_req_CredsTwilioPhone:
- $ref: "./apis/shared/requests/CredsTwilioPhone.yaml"
+_shared_req_CredsNone:
+ $ref: "./apis/shared/requests/CredsNone.yaml"
+_shared_req_CredsAnonymous:
+ $ref: "./apis/shared/requests/CredsAnonymous.yaml"
+_shared_req_CredsCode:
+ $ref: "./apis/shared/requests/CredsCode.yaml"
_shared_req_CredsOIDC:
$ref: "./apis/shared/requests/CredsOIDC.yaml"
-_shared_req_CredsUsername:
- $ref: "./apis/shared/requests/CredsUsername.yaml"
-_shared_req_CredsAPIKey:
- $ref: "./apis/shared/requests/CredsAPIKey.yaml"
-_shared_req_ParamsEmail:
- $ref: "./apis/shared/requests/ParamsEmail.yaml"
-_shared_req_ParamsOIDC:
- $ref: "./apis/shared/requests/ParamsOIDC.yaml"
-_shared_req_ParamsUsername:
- $ref: "./apis/shared/requests/ParamsUsername.yaml"
+_shared_req_CredsPassword:
+ $ref: "./apis/shared/requests/CredsPassword.yaml"
+_shared_req_CredsWebAuthn:
+ $ref: "./apis/shared/requests/CredsWebAuthn.yaml"
+_shared_req_Identifiers:
+ $ref: "./apis/shared/requests/Identifiers.yaml"
+_shared_req_IdentifierString:
+ $ref: "./apis/shared/requests/IdentifierString.yaml"
_shared_req_ParamsNone:
$ref: "./apis/shared/requests/ParamsNone.yaml"
-_shared_req_ParamsSetEmailCredential:
- $ref: "./apis/shared/requests/ParamsSetEmailCredential.yaml"
+_shared_req_ParamsOIDC:
+ $ref: "./apis/shared/requests/ParamsOIDC.yaml"
+_shared_req_ParamsPassword:
+ $ref: "./apis/shared/requests/ParamsPassword.yaml"
+_shared_req_ParamsResetPassword:
+ $ref: "./apis/shared/requests/ParamsResetPassword.yaml"
+_shared_req_ParamsWebAuthn:
+ $ref: "./apis/shared/requests/ParamsWebAuthn.yaml"
_shared_req_app-configs:
$ref: "./apis/shared/requests/app-configs/Request.yaml"
_shared_req_app-configs-org:
@@ -158,14 +168,16 @@ _shared_res_Mfa:
$ref: "./apis/shared/responses/mfa/Response.yaml"
_shared_res_AccountCheck:
$ref: "./apis/shared/responses/AccountCheck.yaml"
-_shared_res_ParamsAPIKey:
- $ref: "./apis/shared/responses/ParamsAPIKey.yaml"
-_shared_res_ParamsOIDC:
- $ref: "./apis/shared/responses/ParamsOIDC.yaml"
+_shared_res_ParamsAnonymous:
+ $ref: "./apis/shared/responses/ParamsAnonymous.yaml"
_shared_res_ParamsNone:
$ref: "./apis/shared/responses/ParamsNone.yaml"
+_shared_res_ParamsOIDC:
+ $ref: "./apis/shared/responses/ParamsOIDC.yaml"
_shared_res_RokwireToken:
$ref: "./apis/shared/responses/RokwireToken.yaml"
+_shared_res_SignInOptions:
+ $ref: "./apis/shared/responses/SignInOptions.yaml"
## end SHARED requests and responses
## SERVICES section
@@ -178,14 +190,22 @@ _services_req_account_auth-type-unlink:
_services_res_account_auth-type-link:
$ref: "./apis/services/account/auth-type/link/response/Response.yaml"
+### account identifier link API
+_services_req_account_identifier-link:
+ $ref: "./apis/services/account/identifier/link/request/Link.yaml"
+_services_req_account_identifier-unlink:
+ $ref: "./apis/services/account/identifier/link/request/Unlink.yaml"
+_services_res_account_identifier-link:
+ $ref: "./apis/services/account/identifier/link/response/Response.yaml"
+
+### identifier_send-verify API
+_services_req_identifier_send-verify:
+ $ref: "./apis/services/identifier/send-verify/request/Request.yaml"
+
### credential_update API
_services_req_credential_update:
$ref: "./apis/services/credential/update/request/Request.yaml"
-### credential_send-verify API
-_services_req_credential_send-verify:
- $ref: "./apis/services/credential/send-verify/request/Request.yaml"
-
### credential_forgot_initiate API
_services_req_credential_forgot_initiate:
$ref: "./apis/services/credential/forgot/initiate/request/Request.yaml"
diff --git a/driver/web/docs/schemas/user/Account.yaml b/driver/web/docs/schemas/user/Account.yaml
index fe6966468..35f732e74 100644
--- a/driver/web/docs/schemas/user/Account.yaml
+++ b/driver/web/docs/schemas/user/Account.yaml
@@ -7,8 +7,6 @@ properties:
type: string
app_org:
$ref: "../application/ApplicationOrganization.yaml"
- username:
- type: string
profile:
$ref: "./Profile.yaml"
privacy:
@@ -25,9 +23,10 @@ properties:
type: boolean
system:
type: boolean
- external_ids:
- type: object
- nullable: true
+ identifiers:
+ type: array
+ items:
+ $ref: "./AccountIdentifier.yaml"
auth_types:
type: array
items:
@@ -57,4 +56,8 @@ properties:
last_access_token_date:
type: string
most_recent_client_version:
- type: string
\ No newline at end of file
+ type: string
+ username:
+ type: string
+ nullable: true
+ deprecated: true
\ No newline at end of file
diff --git a/driver/web/docs/schemas/user/AccountAuthType.yaml b/driver/web/docs/schemas/user/AccountAuthType.yaml
index 410ed119b..14ccf00cb 100644
--- a/driver/web/docs/schemas/user/AccountAuthType.yaml
+++ b/driver/web/docs/schemas/user/AccountAuthType.yaml
@@ -1,20 +1,21 @@
required:
- id
- - code
- - identifier
+ - auth_type_code
type: object
properties:
id:
type: string
+ auth_type_code:
+ type: string
code:
type: string
+ deprecated: true
identifier:
type: string
+ deprecated: true
params:
type: object
additionalProperties: true
nullable: true
active:
type: boolean
- unverified:
- type: boolean
diff --git a/driver/web/docs/schemas/user/AccountIdentifier.yaml b/driver/web/docs/schemas/user/AccountIdentifier.yaml
new file mode 100644
index 000000000..8edab60da
--- /dev/null
+++ b/driver/web/docs/schemas/user/AccountIdentifier.yaml
@@ -0,0 +1,24 @@
+required:
+ - id
+ - code
+ - identifier
+ - verified
+ - linked
+ - sensitive
+type: object
+properties:
+ id:
+ type: string
+ code:
+ type: string
+ identifier:
+ type: string
+ verified:
+ type: boolean
+ linked:
+ type: boolean
+ sensitive:
+ type: boolean
+ account_auth_type_id:
+ type: string
+ nullable: true
\ No newline at end of file
diff --git a/driver/web/docs/schemas/user/PartialAccount.yaml b/driver/web/docs/schemas/user/PartialAccount.yaml
index a0d411601..a97c5f0fb 100644
--- a/driver/web/docs/schemas/user/PartialAccount.yaml
+++ b/driver/web/docs/schemas/user/PartialAccount.yaml
@@ -8,6 +8,7 @@ required:
- roles
- groups
- anonymous
+ - identifiers
- auth_types
- date_created
type: object
@@ -25,8 +26,6 @@ properties:
type: string
system:
type: boolean
- username:
- type: string
permissions:
type: array
items:
@@ -43,10 +42,14 @@ properties:
type: array
items:
type: string
+ identifiers:
+ type: array
+ items:
+ $ref: "./AccountIdentifier.yaml"
auth_types:
type: array
items:
- $ref: "../user/AccountAuthType.yaml"
+ $ref: "./AccountAuthType.yaml"
system_configs:
type: object
nullable: true
@@ -65,6 +68,7 @@ properties:
date_updated:
type: string
nullable: true
- external_ids:
- type: object
- nullable: true
\ No newline at end of file
+ username:
+ type: string
+ nullable: true
+ deprecated: true
\ No newline at end of file
diff --git a/driver/web/docs/schemas/user/Profile.yaml b/driver/web/docs/schemas/user/Profile.yaml
index 08e1f80ce..78cb4027b 100644
--- a/driver/web/docs/schemas/user/Profile.yaml
+++ b/driver/web/docs/schemas/user/Profile.yaml
@@ -14,9 +14,11 @@ properties:
email:
type: string
nullable: true
+ deprecated: true
phone:
type: string
nullable: true
+ deprecated: true
birth_year:
type: integer
nullable: true
diff --git a/driver/web/docs/schemas/user/ProfileNullable.yaml b/driver/web/docs/schemas/user/ProfileNullable.yaml
index 9177a56e3..5b264fcfe 100644
--- a/driver/web/docs/schemas/user/ProfileNullable.yaml
+++ b/driver/web/docs/schemas/user/ProfileNullable.yaml
@@ -13,9 +13,11 @@ properties:
email:
type: string
nullable: true
+ deprecated: true
phone:
type: string
nullable: true
+ deprecated: true
birth_year:
type: integer
nullable: true
diff --git a/driver/web/ui/webauthn-test.html b/driver/web/ui/webauthn-test.html
new file mode 100644
index 000000000..57c309aa5
--- /dev/null
+++ b/driver/web/ui/webauthn-test.html
@@ -0,0 +1,248 @@
+
+
+
+
+
+ WebAuthn Demo
+
+
+
+
+
+ Username:
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/driver/web/utils.go b/driver/web/utils.go
index 5f93a7f6e..6667248c2 100644
--- a/driver/web/utils.go
+++ b/driver/web/utils.go
@@ -17,6 +17,7 @@ package web
import (
"core-building-block/core/model"
Def "core-building-block/driver/web/docs/gen"
+ "core-building-block/utils"
"encoding/json"
"net/http"
@@ -35,26 +36,21 @@ func authBuildLoginResponse(l *logs.Log, loginSession *model.LoginSession) logs.
//account
var accountData *Def.Account
- if loginSession.AccountAuthType != nil {
- account := loginSession.AccountAuthType.Account
- accountData = accountToDef(account)
+ if loginSession.Account != nil {
+ accountData = accountToDef(*loginSession.Account)
}
//params
- var paramsRes Def.SharedResLogin_Params
+ var paramsRes *Def.SharedResLogin_Params
+ var err error
if loginSession.Params != nil {
- paramsBytes, err := json.Marshal(loginSession.Params)
+ paramsRes, err = utils.JSONConvert[Def.SharedResLogin_Params, map[string]interface{}](loginSession.Params)
if err != nil {
- return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.MessageDataType("auth login response params"), nil, err, http.StatusInternalServerError, false)
- }
-
- err = json.Unmarshal(paramsBytes, ¶msRes)
- if err != nil {
- return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.MessageDataType("auth login response params"), nil, err, http.StatusInternalServerError, false)
+ return l.HTTPResponseErrorAction(logutils.ActionParse, logutils.MessageDataType("auth login response params"), nil, err, http.StatusInternalServerError, false)
}
}
- responseData := &Def.SharedResLogin{Token: &rokwireToken, Account: accountData, Params: ¶msRes}
+ responseData := &Def.SharedResLogin{Token: &rokwireToken, Account: accountData, Params: paramsRes}
respData, err := json.Marshal(responseData)
if err != nil {
return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.MessageDataType("auth login response"), nil, err, http.StatusInternalServerError, false)
diff --git a/go.mod b/go.mod
index 49d3cf5bc..e0c9f98fb 100644
--- a/go.mod
+++ b/go.mod
@@ -1,10 +1,11 @@
module core-building-block
-go 1.20
+go 1.21
require (
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/getkin/kin-openapi v0.120.0
+ github.com/go-webauthn/webauthn v0.8.6
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/uuid v1.3.1
github.com/gorilla/mux v1.8.0
@@ -17,7 +18,9 @@ require (
github.com/swaggo/http-swagger v1.3.4
go.mongodb.org/mongo-driver v1.12.1
golang.org/x/crypto v0.14.0
+ golang.org/x/oauth2 v0.13.0
golang.org/x/sync v0.4.0
+ golang.org/x/text v0.13.0
gopkg.in/go-playground/validator.v9 v9.31.0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/yaml.v2 v2.4.0
@@ -28,15 +31,18 @@ require (
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
+ github.com/google/go-tpm v0.9.0 // indirect
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
+ github.com/x448/float16 v0.8.4 // indirect
)
require (
@@ -46,12 +52,15 @@ require (
github.com/boombuler/barcode v1.0.1 // indirect
github.com/casbin/casbin/v2 v2.77.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/fxamacker/cbor/v2 v2.5.0 // indirect
github.com/go-openapi/jsonpointer v0.20.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/spec v0.20.9 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/go-webauthn/x v0.1.4 // indirect
+ github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.5.9 // indirect
@@ -69,7 +78,7 @@ require (
github.com/stretchr/objx v0.5.1 // indirect
github.com/swaggo/files v1.0.1 // indirect
github.com/swaggo/swag v1.16.2 // indirect
- github.com/tidwall/gjson v1.16.0 // indirect
+ github.com/tidwall/gjson v1.17.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
@@ -77,9 +86,7 @@ require (
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
golang.org/x/net v0.16.0 // indirect
- golang.org/x/oauth2 v0.13.0 // indirect
golang.org/x/sys v0.13.0 // indirect
- golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.14.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.31.0 // indirect
diff --git a/go.sum b/go.sum
index 964c10f74..f5e3487a7 100644
--- a/go.sum
+++ b/go.sum
@@ -22,6 +22,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
+github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
+github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg=
github.com/getkin/kin-openapi v0.120.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
@@ -44,10 +46,17 @@ github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/Nu
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
+github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
+github.com/go-webauthn/webauthn v0.8.6 h1:bKMtL1qzd2WTFkf1mFTVbreYrwn7dsYmEPjTq6QN90E=
+github.com/go-webauthn/webauthn v0.8.6/go.mod h1:emwVLMCI5yx9evTTvr0r+aOZCdWJqMfbRhF0MufyUog=
+github.com/go-webauthn/x v0.1.4 h1:sGmIFhcY70l6k7JIDfnjVBiAAFEssga5lXIUXe0GtAs=
+github.com/go-webauthn/x v0.1.4/go.mod h1:75Ug0oK6KYpANh5hDOanfDI+dvPWHk788naJVG/37H8=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
+github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
+github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
@@ -61,6 +70,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
+github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
@@ -80,6 +91,7 @@ github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQs
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -105,6 +117,8 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
@@ -155,14 +169,17 @@ github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4
github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04=
github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
-github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg=
-github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
+github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
+github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
+github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
@@ -185,6 +202,7 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
+golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -255,6 +273,7 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
+gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M=
gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
diff --git a/main.go b/main.go
index c2b531f21..5384883cd 100644
--- a/main.go
+++ b/main.go
@@ -20,10 +20,10 @@ import (
"core-building-block/core/model"
"core-building-block/driven/emailer"
"core-building-block/driven/identitybb"
+ "core-building-block/driven/phoneverifier"
"core-building-block/driven/profilebb"
"core-building-block/driven/storage"
"core-building-block/driver/web"
- "core-building-block/utils"
"os"
"strconv"
"strings"
@@ -64,16 +64,11 @@ func main() {
logger.Infof("Version: %s", Version)
- err := utils.SetRandomSeed()
- if err != nil {
- logger.Error(err.Error())
- }
-
env := envLoader.GetAndLogEnvVar("ROKWIRE_CORE_ENVIRONMENT", true, false) //local, dev, staging, prod
port := envLoader.GetAndLogEnvVar("ROKWIRE_CORE_PORT", false, false)
//Default port of 80
if port == "" {
- port = "80"
+ port = "5000"
}
host := envLoader.GetAndLogEnvVar("ROKWIRE_CORE_HOST", true, false)
@@ -88,7 +83,7 @@ func main() {
mongoDBName := envLoader.GetAndLogEnvVar("ROKWIRE_CORE_MONGO_DATABASE", true, false)
mongoTimeout := envLoader.GetAndLogEnvVar("ROKWIRE_CORE_MONGO_TIMEOUT", false, false)
storageAdapter := storage.NewStorageAdapter(host, mongoDBAuth, mongoDBName, mongoTimeout, logger)
- err = storageAdapter.Start()
+ err := storageAdapter.Start()
if err != nil {
logger.Fatalf("Cannot start the mongoDB adapter: %v", err)
}
@@ -98,6 +93,11 @@ func main() {
twilioToken := envLoader.GetAndLogEnvVar("ROKWIRE_CORE_AUTH_TWILIO_TOKEN", false, true)
twilioServiceSID := envLoader.GetAndLogEnvVar("ROKWIRE_CORE_AUTH_TWILIO_SERVICE_SID", false, true)
+ twilioPhoneVerifier, err := phoneverifier.NewTwilioAdapter(twilioAccountSID, twilioToken, twilioServiceSID)
+ if err != nil {
+ logger.Warnf("Cannot start the twilio phone verifier: %v", err)
+ }
+
smtpHost := envLoader.GetAndLogEnvVar("ROKWIRE_CORE_SMTP_HOST", false, false)
smtpPort := envLoader.GetAndLogEnvVar("ROKWIRE_CORE_SMTP_PORT", false, false)
smtpUser := envLoader.GetAndLogEnvVar("ROKWIRE_CORE_SMTP_USER", false, true)
@@ -111,6 +111,18 @@ func main() {
logger.Infof("Error parsing ROKWIRE_CORE_VERIFY_EMAIL, applying defaults: %v", err)
verifyEmail = true
}
+ verifyWaitTimeRaw := envLoader.GetAndLogEnvVar("ROKWIRE_CORE_VERIFY_WAIT_TIME", false, false)
+ verifyWaitTime, err := strconv.Atoi(verifyWaitTimeRaw)
+ if err != nil {
+ logger.Infof("Error parsing ROKWIRE_CORE_VERIFY_WAIT_TIME, applying defaults: %v", err)
+ verifyWaitTime = 30 // minutes
+ }
+ verifyExpiryRaw := envLoader.GetAndLogEnvVar("ROKWIRE_CORE_VERIFY_EXPIRY", false, false)
+ verifyExpiry, err := strconv.Atoi(verifyExpiryRaw)
+ if err != nil {
+ logger.Infof("Error parsing ROKWIRE_CORE_VERIFY_EXPIRY, applying defaults: %v", err)
+ verifyExpiry = 24 // hours
+ }
emailer := emailer.NewEmailerAdapter(smtpHost, smtpPortNum, smtpUser, smtpPassword, smtpFrom)
@@ -180,8 +192,8 @@ func main() {
FirstParty: true,
}
- authImpl, err := auth.NewAuth(serviceID, host, authPrivKey, authService, storageAdapter, emailer, minTokenExp, maxTokenExp, supportLegacySigs,
- twilioAccountSID, twilioToken, twilioServiceSID, profileBBAdapter, smtpHost, smtpPortNum, smtpUser, smtpPassword, smtpFrom, logger, Version)
+ authImpl, err := auth.NewAuth(serviceID, host, authPrivKey, authService, storageAdapter, emailer, twilioPhoneVerifier, profileBBAdapter,
+ minTokenExp, maxTokenExp, supportLegacySigs, Version, logger)
if err != nil {
logger.Fatalf("Error initializing auth: %v", err)
}
@@ -204,7 +216,7 @@ func main() {
}
//core
- coreAPIs := core.NewCoreAPIs(env, Version, Build, serviceID, storageAdapter, authImpl, systemInitSettings, verifyEmail, logger)
+ coreAPIs := core.NewCoreAPIs(env, Version, Build, serviceID, storageAdapter, authImpl, systemInitSettings, verifyEmail, verifyWaitTime, verifyExpiry, logger)
coreAPIs.Start()
// read CORS parameters from stored env config
diff --git a/utils/utils.go b/utils/utils.go
index 9cd6d00af..528059243 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -17,7 +17,6 @@ package utils
import (
crand "crypto/rand"
"crypto/sha256"
- "encoding/binary"
"encoding/json"
"fmt"
"math/rand"
@@ -57,22 +56,10 @@ const (
special string = "!@#$%^&*()"
)
-// SetRandomSeed sets the seed for random number generation
-func SetRandomSeed() error {
- seed := make([]byte, 8)
- _, err := crand.Read(seed)
- if err != nil {
- return errors.WrapErrorAction(logutils.ActionGenerate, "math/rand seed", nil, err)
- }
-
- rand.Seed(int64(binary.LittleEndian.Uint64(seed)))
- return nil
-}
-
// GenerateRandomBytes returns securely generated random bytes
func GenerateRandomBytes(n int) ([]byte, error) {
b := make([]byte, n)
- _, err := rand.Read(b)
+ _, err := crand.Read(b)
if err != nil {
return nil, err
}
@@ -81,13 +68,13 @@ func GenerateRandomBytes(n int) ([]byte, error) {
}
// GenerateRandomString returns a URL-safe, base64 encoded securely generated random string
-func GenerateRandomString(s int) (string, error) {
+func GenerateRandomString(s int) string {
chars := []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
b := make([]rune, s)
for i := range b {
b[i] = chars[rand.Intn(len(chars))]
}
- return string(b), nil
+ return string(b)
}
// GenerateRandomInt returns a random integer between 0 and max
@@ -108,13 +95,36 @@ func GenerateRandomPassword(s int) string {
return string(password)
}
-// ConvertToJSON converts to json
-func ConvertToJSON(data interface{}) ([]byte, error) {
- dataJSON, err := json.Marshal(data)
+// JSONConvert json marshals and unmarshals data into result (result should be passed as a pointer)
+func JSONConvert[T any, F any](val F) (*T, error) {
+ if IsNil(val) {
+ return nil, nil
+ }
+
+ bytes, err := json.Marshal(val)
+ if err != nil {
+ return nil, errors.WrapErrorAction(logutils.ActionMarshal, "value", nil, err)
+ }
+
+ var out T
+ err = json.Unmarshal(bytes, &out)
if err != nil {
- return nil, errors.WrapErrorAction(logutils.ActionMarshal, "map to json", nil, err)
+ return nil, errors.WrapErrorAction(logutils.ActionUnmarshal, "value", nil, err)
+ }
+
+ return &out, nil
+}
+
+// IsNil determines whether the given interface has a nil value
+func IsNil(i interface{}) bool {
+ if i == nil {
+ return true
}
- return dataJSON, nil
+ switch reflect.TypeOf(i).Kind() {
+ case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice:
+ return reflect.ValueOf(i).IsNil()
+ }
+ return false
}
// DeepEqual checks whether a and b are “deeply equal,”