mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 21:27:22 +00:00
feat: Identity brokering (#730)
* feat: add/ remove external idps * feat: external idp add /remove * fix: auth proto * fix: handle login * feat: loginpolicy on authrequest * feat: idp providers on login * feat: link external idp * fix: check login policy on check username * feat: add mapping fields for idp config * feat: use user org id if existing * feat: use user org id if existing * feat: register external user * feat: register external user * feat: user linking * feat: user linking * feat: design external login * feat: design external login * fix: tests * fix: regenerate login design * feat: next step test linking process * feat: next step test linking process * feat: cascade remove external idps on user * fix: tests * fix: tests * feat: external idp requsts on users * fix: generate protos * feat: login styles * feat: login styles * fix: link user * fix: register user on specifig org * fix: user linking * fix: register external, linking auto * fix: remove unnecessary request from proto * fix: tests * fix: new oidc package * fix: migration version * fix: policy permissions * Update internal/ui/login/static/i18n/en.yaml Co-authored-by: Livio Amstutz <livio.a@gmail.com> * Update internal/ui/login/static/i18n/en.yaml Co-authored-by: Livio Amstutz <livio.a@gmail.com> * Update internal/ui/login/handler/renderer.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * Update internal/ui/login/handler/renderer.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * fix: pr requests * Update internal/ui/login/handler/link_users_handler.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * fix: pr requests * fix: pr requests * fix: pr requests * fix: login name size * fix: profile image light * fix: colors * fix: pr requests * fix: remove redirect uri validator * fix: remove redirect uri validator Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
parent
1d542a0c57
commit
320ddfa46d
@ -113,7 +113,7 @@ func startZitadel(configPaths []string) {
|
|||||||
func startUI(ctx context.Context, conf *Config, authRepo *auth_es.EsRepository) {
|
func startUI(ctx context.Context, conf *Config, authRepo *auth_es.EsRepository) {
|
||||||
uis := ui.Create(conf.UI)
|
uis := ui.Create(conf.UI)
|
||||||
if *loginEnabled {
|
if *loginEnabled {
|
||||||
login, prefix := login.Start(conf.UI.Login, authRepo, *localDevMode)
|
login, prefix := login.Start(conf.UI.Login, authRepo, conf.SystemDefaults, *localDevMode)
|
||||||
uis.RegisterHandler(prefix, login.Handler())
|
uis.RegisterHandler(prefix, login.Handler())
|
||||||
}
|
}
|
||||||
if *consoleEnabled {
|
if *consoleEnabled {
|
||||||
|
@ -222,6 +222,7 @@ UI:
|
|||||||
Port: 50003
|
Port: 50003
|
||||||
Login:
|
Login:
|
||||||
Handler:
|
Handler:
|
||||||
|
BaseURL: '$ZITADEL_ACCOUNTS'
|
||||||
OidcAuthCallbackURL: '$ZITADEL_AUTHORIZE/authorize/'
|
OidcAuthCallbackURL: '$ZITADEL_AUTHORIZE/authorize/'
|
||||||
ZitadelURL: '$ZITADEL_CONSOLE'
|
ZitadelURL: '$ZITADEL_CONSOLE'
|
||||||
LanguageCookieName: 'caos.zitadel.login.lang'
|
LanguageCookieName: 'caos.zitadel.login.lang'
|
||||||
|
@ -81,7 +81,7 @@
|
|||||||
|
|
||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
<div class="btn-wrapper">
|
<div class="btn-wrapper">
|
||||||
<ng-template appHasRole [appHasRole]="['iam.policy.write']">
|
<ng-template appHasRole [appHasRole]="['policy.write']">
|
||||||
<button [disabled]="loginPolicy" [routerLink]="[ 'policy', PolicyComponentType.LOGIN,'create' ]"
|
<button [disabled]="loginPolicy" [routerLink]="[ 'policy', PolicyComponentType.LOGIN,'create' ]"
|
||||||
color="primary" mat-raised-button>{{'ORG.POLICY.BTN_INSTALL' | translate}}</button>
|
color="primary" mat-raised-button>{{'ORG.POLICY.BTN_INSTALL' | translate}}</button>
|
||||||
<button [disabled]="!loginPolicy" [routerLink]="[ 'policy', PolicyComponentType.LOGIN ]"
|
<button [disabled]="!loginPolicy" [routerLink]="[ 'policy', PolicyComponentType.LOGIN ]"
|
||||||
@ -90,4 +90,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,6 +9,8 @@ import (
|
|||||||
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
|
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
|
||||||
iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||||
org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||||
|
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||||
|
usr_es "github.com/caos/zitadel/internal/user/repository/eventsourcing"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
@ -19,6 +21,7 @@ type IAMRepository struct {
|
|||||||
SearchLimit uint64
|
SearchLimit uint64
|
||||||
*iam_es.IAMEventstore
|
*iam_es.IAMEventstore
|
||||||
OrgEvents *org_es.OrgEventstore
|
OrgEvents *org_es.OrgEventstore
|
||||||
|
UserEvents *usr_es.UserEventstore
|
||||||
View *admin_view.View
|
View *admin_view.View
|
||||||
SystemDefaults systemdefaults.SystemDefaults
|
SystemDefaults systemdefaults.SystemDefaults
|
||||||
Roles []string
|
Roles []string
|
||||||
@ -83,7 +86,7 @@ func (repo *IAMRepository) IDPConfigByID(ctx context.Context, idpConfigID string
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return iam_es_model.IdpConfigViewToModel(idp), nil
|
return iam_es_model.IDPConfigViewToModel(idp), nil
|
||||||
}
|
}
|
||||||
func (repo *IAMRepository) AddOIDCIDPConfig(ctx context.Context, idp *iam_model.IDPConfig) (*iam_model.IDPConfig, error) {
|
func (repo *IAMRepository) AddOIDCIDPConfig(ctx context.Context, idp *iam_model.IDPConfig) (*iam_model.IDPConfig, error) {
|
||||||
idp.AggregateID = repo.SystemDefaults.IamID
|
idp.AggregateID = repo.SystemDefaults.IamID
|
||||||
@ -128,7 +131,19 @@ func (repo *IAMRepository) RemoveIDPConfig(ctx context.Context, idpConfigID stri
|
|||||||
}
|
}
|
||||||
aggregates = append(aggregates, providerAgg)
|
aggregates = append(aggregates, providerAgg)
|
||||||
}
|
}
|
||||||
|
externalIDPs, err := repo.View.ExternalIDPsByIDPConfigID(idpConfigID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, externalIDP := range externalIDPs {
|
||||||
|
idpRemove := &usr_model.ExternalIDP{ObjectRoot: es_models.ObjectRoot{AggregateID: externalIDP.UserID}, IDPConfigID: externalIDP.IDPConfigID, UserID: externalIDP.ExternalUserID}
|
||||||
|
idpAgg := make([]*es_models.Aggregate, 0)
|
||||||
|
_, idpAgg, err = repo.UserEvents.PrepareRemoveExternalIDP(ctx, idpRemove, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
aggregates = append(aggregates, idpAgg...)
|
||||||
|
}
|
||||||
return es_sdk.PushAggregates(ctx, repo.Eventstore.PushAggregates, nil, aggregates...)
|
return es_sdk.PushAggregates(ctx, repo.Eventstore.PushAggregates, nil, aggregates...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,7 +218,27 @@ func (repo *IAMRepository) AddIDPProviderToLoginPolicy(ctx context.Context, prov
|
|||||||
return repo.IAMEventstore.AddIDPProviderToLoginPolicy(ctx, provider)
|
return repo.IAMEventstore.AddIDPProviderToLoginPolicy(ctx, provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *IAMRepository) RemoveIdpProviderFromIdpProvider(ctx context.Context, provider *iam_model.IDPProvider) error {
|
func (repo *IAMRepository) RemoveIDPProviderFromIDPProvider(ctx context.Context, provider *iam_model.IDPProvider) error {
|
||||||
|
aggregates := make([]*es_models.Aggregate, 0)
|
||||||
provider.AggregateID = repo.SystemDefaults.IamID
|
provider.AggregateID = repo.SystemDefaults.IamID
|
||||||
return repo.IAMEventstore.RemoveIDPProviderFromLoginPolicy(ctx, provider)
|
_, removeAgg, err := repo.IAMEventstore.PrepareRemoveIDPProviderFromLoginPolicy(ctx, provider)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
aggregates = append(aggregates, removeAgg)
|
||||||
|
|
||||||
|
externalIDPs, err := repo.View.ExternalIDPsByIDPConfigID(provider.IdpConfigID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, externalIDP := range externalIDPs {
|
||||||
|
idpRemove := &usr_model.ExternalIDP{ObjectRoot: es_models.ObjectRoot{AggregateID: externalIDP.UserID}, IDPConfigID: externalIDP.IDPConfigID, UserID: externalIDP.ExternalUserID}
|
||||||
|
idpAgg := make([]*es_models.Aggregate, 0)
|
||||||
|
_, idpAgg, err = repo.UserEvents.PrepareRemoveExternalIDP(ctx, idpRemove, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
aggregates = append(aggregates, idpAgg...)
|
||||||
|
}
|
||||||
|
return es_sdk.PushAggregates(ctx, repo.Eventstore.PushAggregates, nil, aggregates...)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,126 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/caos/zitadel/internal/iam/repository/eventsourcing"
|
||||||
|
org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||||
|
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||||
|
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||||
|
usr_view_model "github.com/caos/zitadel/internal/user/repository/view/model"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||||
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
|
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExternalIDP struct {
|
||||||
|
handler
|
||||||
|
systemDefaults systemdefaults.SystemDefaults
|
||||||
|
iamEvents *eventsourcing.IAMEventstore
|
||||||
|
orgEvents *org_es.OrgEventstore
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
externalIDPTable = "adminapi.user_external_idps"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *ExternalIDP) ViewModel() string {
|
||||||
|
return externalIDPTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) EventQuery() (*models.SearchQuery, error) {
|
||||||
|
sequence, err := m.view.GetLatestExternalIDPSequence()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return es_models.NewSearchQuery().
|
||||||
|
AggregateTypeFilter(model.UserAggregate, iam_es_model.IAMAggregate, org_es_model.OrgAggregate).
|
||||||
|
LatestSequenceFilter(sequence.CurrentSequence), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) Reduce(event *models.Event) (err error) {
|
||||||
|
switch event.AggregateType {
|
||||||
|
case model.UserAggregate:
|
||||||
|
err = m.processUser(event)
|
||||||
|
case iam_es_model.IAMAggregate, org_es_model.OrgAggregate:
|
||||||
|
err = m.processIdpConfig(event)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) processUser(event *models.Event) (err error) {
|
||||||
|
externalIDP := new(usr_view_model.ExternalIDPView)
|
||||||
|
switch event.Type {
|
||||||
|
case model.HumanExternalIDPAdded:
|
||||||
|
err = externalIDP.AppendEvent(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = m.fillData(externalIDP)
|
||||||
|
case model.HumanExternalIDPRemoved, model.HumanExternalIDPCascadeRemoved:
|
||||||
|
err = externalIDP.SetData(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return m.view.DeleteExternalIDP(externalIDP.ExternalUserID, externalIDP.IDPConfigID, event.Sequence)
|
||||||
|
default:
|
||||||
|
return m.view.ProcessedExternalIDPSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return m.view.PutExternalIDP(externalIDP, externalIDP.Sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) processIdpConfig(event *models.Event) (err error) {
|
||||||
|
switch event.Type {
|
||||||
|
case iam_es_model.IDPConfigChanged, org_es_model.IDPConfigChanged:
|
||||||
|
config := new(iam_model.IDPConfig)
|
||||||
|
config.AppendEvent(event)
|
||||||
|
exterinalIDPs, err := m.view.ExternalIDPsByIDPConfigID(config.IDPConfigID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if event.AggregateType == iam_es_model.IAMAggregate {
|
||||||
|
config, err = m.iamEvents.GetIDPConfig(context.Background(), config.AggregateID, config.IDPConfigID)
|
||||||
|
} else {
|
||||||
|
config, err = m.orgEvents.GetIDPConfig(context.Background(), config.AggregateID, config.IDPConfigID)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, provider := range exterinalIDPs {
|
||||||
|
m.fillConfigData(provider, config)
|
||||||
|
}
|
||||||
|
return m.view.PutExternalIDPs(event.Sequence, exterinalIDPs...)
|
||||||
|
default:
|
||||||
|
return m.view.ProcessedExternalIDPSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) fillData(externalIDP *usr_view_model.ExternalIDPView) error {
|
||||||
|
config, err := m.orgEvents.GetIDPConfig(context.Background(), externalIDP.ResourceOwner, externalIDP.IDPConfigID)
|
||||||
|
if caos_errs.IsNotFound(err) {
|
||||||
|
config, err = m.iamEvents.GetIDPConfig(context.Background(), m.systemDefaults.IamID, externalIDP.IDPConfigID)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.fillConfigData(externalIDP, config)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) fillConfigData(externalIDP *usr_view_model.ExternalIDPView, config *iam_model.IDPConfig) {
|
||||||
|
externalIDP.IDPName = config.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) OnError(event *models.Event, err error) error {
|
||||||
|
logging.LogWithFields("SPOOL-4Rsu8", "id", event.AggregateID).WithError(err).Warn("something went wrong in idp provider handler")
|
||||||
|
return spooler.HandleError(event, err, m.view.GetLatestExternalIDPFailedEvent, m.view.ProcessedExternalIDPFailedEvent, m.view.ProcessedExternalIDPSequence, m.errorCountUntilSkip)
|
||||||
|
}
|
@ -87,6 +87,7 @@ func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, r
|
|||||||
IAMRepository: eventstore.IAMRepository{
|
IAMRepository: eventstore.IAMRepository{
|
||||||
IAMEventstore: iam,
|
IAMEventstore: iam,
|
||||||
OrgEvents: org,
|
OrgEvents: org,
|
||||||
|
UserEvents: user,
|
||||||
View: view,
|
View: view,
|
||||||
SystemDefaults: systemDefaults,
|
SystemDefaults: systemDefaults,
|
||||||
SearchLimit: conf.SearchLimit,
|
SearchLimit: conf.SearchLimit,
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||||
|
"github.com/caos/zitadel/internal/user/repository/view"
|
||||||
|
"github.com/caos/zitadel/internal/user/repository/view/model"
|
||||||
|
global_view "github.com/caos/zitadel/internal/view/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
externalIDPTable = "adminapi.user_external_idps"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *View) ExternalIDPByExternalUserIDAndIDPConfigID(externalUserID, idpConfigID string) (*model.ExternalIDPView, error) {
|
||||||
|
return view.ExternalIDPByExternalUserIDAndIDPConfigID(v.Db, externalIDPTable, externalUserID, idpConfigID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ExternalIDPByExternalUserIDAndIDPConfigIDAndResourceOwner(externalUserID, idpConfigID, resourceOwner string) (*model.ExternalIDPView, error) {
|
||||||
|
return view.ExternalIDPByExternalUserIDAndIDPConfigIDAndResourceOwner(v.Db, externalIDPTable, externalUserID, idpConfigID, resourceOwner)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ExternalIDPsByIDPConfigID(idpConfigID string) ([]*model.ExternalIDPView, error) {
|
||||||
|
return view.ExternalIDPsByIDPConfigID(v.Db, externalIDPTable, idpConfigID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ExternalIDPsByUserID(userID string) ([]*model.ExternalIDPView, error) {
|
||||||
|
return view.ExternalIDPsByUserID(v.Db, externalIDPTable, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) SearchExternalIDPs(request *usr_model.ExternalIDPSearchRequest) ([]*model.ExternalIDPView, uint64, error) {
|
||||||
|
return view.SearchExternalIDPs(v.Db, externalIDPTable, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) PutExternalIDP(externalIDP *model.ExternalIDPView, sequence uint64) error {
|
||||||
|
err := view.PutExternalIDP(v.Db, externalIDPTable, externalIDP)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedExternalIDPSequence(sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) PutExternalIDPs(sequence uint64, externalIDPs ...*model.ExternalIDPView) error {
|
||||||
|
err := view.PutExternalIDPs(v.Db, externalIDPTable, externalIDPs...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedExternalIDPSequence(sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) DeleteExternalIDP(externalUserID, idpConfigID string, eventSequence uint64) error {
|
||||||
|
err := view.DeleteExternalIDP(v.Db, externalIDPTable, externalUserID, idpConfigID)
|
||||||
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedExternalIDPSequence(eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestExternalIDPSequence() (*global_view.CurrentSequence, error) {
|
||||||
|
return v.latestSequence(externalIDPTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedExternalIDPSequence(eventSequence uint64) error {
|
||||||
|
return v.saveCurrentSequence(externalIDPTable, eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestExternalIDPFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||||
|
return v.latestFailedEvent(externalIDPTable, sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedExternalIDPFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||||
|
return v.saveFailedEvent(failedEvent)
|
||||||
|
}
|
@ -13,10 +13,12 @@ func createOidcIdpToModel(idp *admin.OidcIdpConfigCreate) *iam_model.IDPConfig {
|
|||||||
LogoSrc: idp.LogoSrc,
|
LogoSrc: idp.LogoSrc,
|
||||||
Type: iam_model.IDPConfigTypeOIDC,
|
Type: iam_model.IDPConfigTypeOIDC,
|
||||||
OIDCConfig: &iam_model.OIDCIDPConfig{
|
OIDCConfig: &iam_model.OIDCIDPConfig{
|
||||||
ClientID: idp.ClientId,
|
ClientID: idp.ClientId,
|
||||||
ClientSecretString: idp.ClientSecret,
|
ClientSecretString: idp.ClientSecret,
|
||||||
Issuer: idp.Issuer,
|
Issuer: idp.Issuer,
|
||||||
Scopes: idp.Scopes,
|
Scopes: idp.Scopes,
|
||||||
|
IDPDisplayNameMapping: oidcMappingFieldToModel(idp.IdpDisplayNameMapping),
|
||||||
|
UsernameMapping: oidcMappingFieldToModel(idp.UsernameMapping),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -31,11 +33,13 @@ func updateIdpToModel(idp *admin.IdpUpdate) *iam_model.IDPConfig {
|
|||||||
|
|
||||||
func updateOidcIdpToModel(idp *admin.OidcIdpConfigUpdate) *iam_model.OIDCIDPConfig {
|
func updateOidcIdpToModel(idp *admin.OidcIdpConfigUpdate) *iam_model.OIDCIDPConfig {
|
||||||
return &iam_model.OIDCIDPConfig{
|
return &iam_model.OIDCIDPConfig{
|
||||||
IDPConfigID: idp.IdpId,
|
IDPConfigID: idp.IdpId,
|
||||||
ClientID: idp.ClientId,
|
ClientID: idp.ClientId,
|
||||||
ClientSecretString: idp.ClientSecret,
|
ClientSecretString: idp.ClientSecret,
|
||||||
Issuer: idp.Issuer,
|
Issuer: idp.Issuer,
|
||||||
Scopes: idp.Scopes,
|
Scopes: idp.Scopes,
|
||||||
|
IDPDisplayNameMapping: oidcMappingFieldToModel(idp.IdpDisplayNameMapping),
|
||||||
|
UsernameMapping: oidcMappingFieldToModel(idp.UsernameMapping),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,9 +109,11 @@ func idpConfigViewFromModel(idp *iam_model.IDPConfigView) *admin.IdpView_OidcCon
|
|||||||
|
|
||||||
func oidcIdpConfigViewFromModel(idp *iam_model.IDPConfigView) *admin.OidcIdpConfigView {
|
func oidcIdpConfigViewFromModel(idp *iam_model.IDPConfigView) *admin.OidcIdpConfigView {
|
||||||
return &admin.OidcIdpConfigView{
|
return &admin.OidcIdpConfigView{
|
||||||
ClientId: idp.OIDCClientID,
|
ClientId: idp.OIDCClientID,
|
||||||
Issuer: idp.OIDCIssuer,
|
Issuer: idp.OIDCIssuer,
|
||||||
Scopes: idp.OIDCScopes,
|
Scopes: idp.OIDCScopes,
|
||||||
|
IdpDisplayNameMapping: oidcMappingFieldFromModel(idp.OIDCIDPDisplayNameMapping),
|
||||||
|
UsernameMapping: oidcMappingFieldFromModel(idp.OIDCUsernameMapping),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +128,28 @@ func idpConfigStateFromModel(state iam_model.IDPConfigState) admin.IdpState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func oidcMappingFieldFromModel(field iam_model.OIDCMappingField) admin.OIDCMappingField {
|
||||||
|
switch field {
|
||||||
|
case iam_model.OIDCMappingFieldPreferredLoginName:
|
||||||
|
return admin.OIDCMappingField_OIDCMAPPINGFIELD_PREFERRED_USERNAME
|
||||||
|
case iam_model.OIDCMappingFieldEmail:
|
||||||
|
return admin.OIDCMappingField_OIDCMAPPINGFIELD_EMAIL
|
||||||
|
default:
|
||||||
|
return admin.OIDCMappingField_OIDCMAPPINGFIELD_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func oidcMappingFieldToModel(field admin.OIDCMappingField) iam_model.OIDCMappingField {
|
||||||
|
switch field {
|
||||||
|
case admin.OIDCMappingField_OIDCMAPPINGFIELD_PREFERRED_USERNAME:
|
||||||
|
return iam_model.OIDCMappingFieldPreferredLoginName
|
||||||
|
case admin.OIDCMappingField_OIDCMAPPINGFIELD_EMAIL:
|
||||||
|
return iam_model.OIDCMappingFieldEmail
|
||||||
|
default:
|
||||||
|
return iam_model.OIDCMappingFieldUnspecified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func idpConfigSearchRequestToModel(request *admin.IdpSearchRequest) *iam_model.IDPConfigSearchRequest {
|
func idpConfigSearchRequestToModel(request *admin.IdpSearchRequest) *iam_model.IDPConfigSearchRequest {
|
||||||
return &iam_model.IDPConfigSearchRequest{
|
return &iam_model.IDPConfigSearchRequest{
|
||||||
Limit: request.Limit,
|
Limit: request.Limit,
|
||||||
|
@ -122,6 +122,19 @@ func (s *Server) ChangeMyPassword(ctx context.Context, request *auth.PasswordCha
|
|||||||
return &empty.Empty{}, err
|
return &empty.Empty{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) SearchMyExternalIDPs(ctx context.Context, request *auth.ExternalIDPSearchRequest) (*auth.ExternalIDPSearchResponse, error) {
|
||||||
|
externalIDP, err := s.repo.SearchMyExternalIDPs(ctx, externalIDPSearchRequestToModel(request))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return externalIDPSearchResponseFromModel(externalIDP), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RemoveMyExternalIDP(ctx context.Context, request *auth.ExternalIDPRemoveRequest) (*empty.Empty, error) {
|
||||||
|
err := s.repo.RemoveMyExternalIDP(ctx, externalIDPRemoveToModel(ctx, request))
|
||||||
|
return &empty.Empty{}, err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) GetMyPasswordComplexityPolicy(ctx context.Context, _ *empty.Empty) (*auth.PasswordComplexityPolicy, error) {
|
func (s *Server) GetMyPasswordComplexityPolicy(ctx context.Context, _ *empty.Empty) (*auth.PasswordComplexityPolicy, error) {
|
||||||
policy, err := s.repo.GetMyPasswordComplexityPolicy(ctx)
|
policy, err := s.repo.GetMyPasswordComplexityPolicy(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -3,7 +3,6 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
"github.com/golang/protobuf/ptypes"
|
"github.com/golang/protobuf/ptypes"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
@ -242,6 +241,69 @@ func updateAddressToModel(ctx context.Context, address *auth.UpdateUserAddressRe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func externalIDPSearchRequestToModel(request *auth.ExternalIDPSearchRequest) *usr_model.ExternalIDPSearchRequest {
|
||||||
|
return &usr_model.ExternalIDPSearchRequest{
|
||||||
|
Limit: request.Limit,
|
||||||
|
Offset: request.Offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func externalIDPRemoveToModel(ctx context.Context, idp *auth.ExternalIDPRemoveRequest) *usr_model.ExternalIDP {
|
||||||
|
return &usr_model.ExternalIDP{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: authz.GetCtxData(ctx).UserID},
|
||||||
|
IDPConfigID: idp.IdpConfigId,
|
||||||
|
UserID: idp.ExternalUserId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func externalIDPResponseFromModel(idp *usr_model.ExternalIDP) *auth.ExternalIDPResponse {
|
||||||
|
return &auth.ExternalIDPResponse{
|
||||||
|
IdpConfigId: idp.IDPConfigID,
|
||||||
|
UserId: idp.UserID,
|
||||||
|
DisplayName: idp.DisplayName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func externalIDPSearchResponseFromModel(response *usr_model.ExternalIDPSearchResponse) *auth.ExternalIDPSearchResponse {
|
||||||
|
viewTimestamp, err := ptypes.TimestampProto(response.Timestamp)
|
||||||
|
logging.Log("GRPC-3h8is").OnError(err).Debug("unable to parse timestamp")
|
||||||
|
|
||||||
|
return &auth.ExternalIDPSearchResponse{
|
||||||
|
Offset: response.Offset,
|
||||||
|
Limit: response.Limit,
|
||||||
|
TotalResult: response.TotalResult,
|
||||||
|
ProcessedSequence: response.Sequence,
|
||||||
|
ViewTimestamp: viewTimestamp,
|
||||||
|
Result: externalIDPViewsFromModel(response.Result),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func externalIDPViewsFromModel(externalIDPs []*usr_model.ExternalIDPView) []*auth.ExternalIDPView {
|
||||||
|
converted := make([]*auth.ExternalIDPView, len(externalIDPs))
|
||||||
|
for i, externalIDP := range externalIDPs {
|
||||||
|
converted[i] = externalIDPViewFromModel(externalIDP)
|
||||||
|
}
|
||||||
|
return converted
|
||||||
|
}
|
||||||
|
|
||||||
|
func externalIDPViewFromModel(externalIDP *usr_model.ExternalIDPView) *auth.ExternalIDPView {
|
||||||
|
creationDate, err := ptypes.TimestampProto(externalIDP.CreationDate)
|
||||||
|
logging.Log("GRPC-Sj8dw").OnError(err).Debug("unable to parse timestamp")
|
||||||
|
|
||||||
|
changeDate, err := ptypes.TimestampProto(externalIDP.ChangeDate)
|
||||||
|
logging.Log("GRPC-Nf8ue").OnError(err).Debug("unable to parse timestamp")
|
||||||
|
|
||||||
|
return &auth.ExternalIDPView{
|
||||||
|
UserId: externalIDP.UserID,
|
||||||
|
IdpConfigId: externalIDP.IDPConfigID,
|
||||||
|
ExternalUserId: externalIDP.ExternalUserID,
|
||||||
|
ExternalUserDisplayName: externalIDP.UserDisplayName,
|
||||||
|
IdpName: externalIDP.IDPName,
|
||||||
|
CreationDate: creationDate,
|
||||||
|
ChangeDate: changeDate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func otpFromModel(otp *usr_model.OTP) *auth.MfaOtpResponse {
|
func otpFromModel(otp *usr_model.OTP) *auth.MfaOtpResponse {
|
||||||
return &auth.MfaOtpResponse{
|
return &auth.MfaOtpResponse{
|
||||||
UserId: otp.AggregateID,
|
UserId: otp.AggregateID,
|
||||||
|
@ -13,10 +13,12 @@ func createOidcIdpToModel(idp *management.OidcIdpConfigCreate) *iam_model.IDPCon
|
|||||||
LogoSrc: idp.LogoSrc,
|
LogoSrc: idp.LogoSrc,
|
||||||
Type: iam_model.IDPConfigTypeOIDC,
|
Type: iam_model.IDPConfigTypeOIDC,
|
||||||
OIDCConfig: &iam_model.OIDCIDPConfig{
|
OIDCConfig: &iam_model.OIDCIDPConfig{
|
||||||
ClientID: idp.ClientId,
|
ClientID: idp.ClientId,
|
||||||
ClientSecretString: idp.ClientSecret,
|
ClientSecretString: idp.ClientSecret,
|
||||||
Issuer: idp.Issuer,
|
Issuer: idp.Issuer,
|
||||||
Scopes: idp.Scopes,
|
Scopes: idp.Scopes,
|
||||||
|
IDPDisplayNameMapping: oidcMappingFieldToModel(idp.IdpDisplayNameMapping),
|
||||||
|
UsernameMapping: oidcMappingFieldToModel(idp.UsernameMapping),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -31,11 +33,13 @@ func updateIdpToModel(idp *management.IdpUpdate) *iam_model.IDPConfig {
|
|||||||
|
|
||||||
func updateOidcIdpToModel(idp *management.OidcIdpConfigUpdate) *iam_model.OIDCIDPConfig {
|
func updateOidcIdpToModel(idp *management.OidcIdpConfigUpdate) *iam_model.OIDCIDPConfig {
|
||||||
return &iam_model.OIDCIDPConfig{
|
return &iam_model.OIDCIDPConfig{
|
||||||
IDPConfigID: idp.IdpId,
|
IDPConfigID: idp.IdpId,
|
||||||
ClientID: idp.ClientId,
|
ClientID: idp.ClientId,
|
||||||
ClientSecretString: idp.ClientSecret,
|
ClientSecretString: idp.ClientSecret,
|
||||||
Issuer: idp.Issuer,
|
Issuer: idp.Issuer,
|
||||||
Scopes: idp.Scopes,
|
Scopes: idp.Scopes,
|
||||||
|
IDPDisplayNameMapping: oidcMappingFieldToModel(idp.IdpDisplayNameMapping),
|
||||||
|
UsernameMapping: oidcMappingFieldToModel(idp.UsernameMapping),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,9 +93,11 @@ func idpConfigFromModel(idp *iam_model.IDPConfig) *management.Idp_OidcConfig {
|
|||||||
|
|
||||||
func oidcIdpConfigFromModel(idp *iam_model.OIDCIDPConfig) *management.OidcIdpConfig {
|
func oidcIdpConfigFromModel(idp *iam_model.OIDCIDPConfig) *management.OidcIdpConfig {
|
||||||
return &management.OidcIdpConfig{
|
return &management.OidcIdpConfig{
|
||||||
ClientId: idp.ClientID,
|
ClientId: idp.ClientID,
|
||||||
Issuer: idp.Issuer,
|
Issuer: idp.Issuer,
|
||||||
Scopes: idp.Scopes,
|
Scopes: idp.Scopes,
|
||||||
|
IdpDisplayNameMapping: oidcMappingFieldFromModel(idp.IDPDisplayNameMapping),
|
||||||
|
UsernameMapping: oidcMappingFieldFromModel(idp.UsernameMapping),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,9 +112,11 @@ func idpConfigViewFromModel(idp *iam_model.IDPConfigView) *management.IdpView_Oi
|
|||||||
|
|
||||||
func oidcIdpConfigViewFromModel(idp *iam_model.IDPConfigView) *management.OidcIdpConfigView {
|
func oidcIdpConfigViewFromModel(idp *iam_model.IDPConfigView) *management.OidcIdpConfigView {
|
||||||
return &management.OidcIdpConfigView{
|
return &management.OidcIdpConfigView{
|
||||||
ClientId: idp.OIDCClientID,
|
ClientId: idp.OIDCClientID,
|
||||||
Issuer: idp.OIDCIssuer,
|
Issuer: idp.OIDCIssuer,
|
||||||
Scopes: idp.OIDCScopes,
|
Scopes: idp.OIDCScopes,
|
||||||
|
IdpDisplayNameMapping: oidcMappingFieldFromModel(idp.OIDCIDPDisplayNameMapping),
|
||||||
|
UsernameMapping: oidcMappingFieldFromModel(idp.OIDCUsernameMapping),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,3 +189,25 @@ func idpConfigsFromView(viewIdps []*iam_model.IDPConfigView) []*management.IdpVi
|
|||||||
}
|
}
|
||||||
return idps
|
return idps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func oidcMappingFieldFromModel(field iam_model.OIDCMappingField) management.OIDCMappingField {
|
||||||
|
switch field {
|
||||||
|
case iam_model.OIDCMappingFieldPreferredLoginName:
|
||||||
|
return management.OIDCMappingField_OIDCMAPPINGFIELD_PREFERRED_USERNAME
|
||||||
|
case iam_model.OIDCMappingFieldEmail:
|
||||||
|
return management.OIDCMappingField_OIDCMAPPINGFIELD_EMAIL
|
||||||
|
default:
|
||||||
|
return management.OIDCMappingField_OIDCMAPPINGFIELD_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func oidcMappingFieldToModel(field management.OIDCMappingField) iam_model.OIDCMappingField {
|
||||||
|
switch field {
|
||||||
|
case management.OIDCMappingField_OIDCMAPPINGFIELD_PREFERRED_USERNAME:
|
||||||
|
return iam_model.OIDCMappingFieldPreferredLoginName
|
||||||
|
case management.OIDCMappingField_OIDCMAPPINGFIELD_EMAIL:
|
||||||
|
return iam_model.OIDCMappingFieldEmail
|
||||||
|
default:
|
||||||
|
return iam_model.OIDCMappingFieldUnspecified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,7 +2,6 @@ package management
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
"github.com/caos/zitadel/internal/errors"
|
"github.com/caos/zitadel/internal/errors"
|
||||||
"github.com/caos/zitadel/pkg/grpc/management"
|
"github.com/caos/zitadel/pkg/grpc/management"
|
||||||
@ -196,6 +195,19 @@ func (s *Server) SetInitialPassword(ctx context.Context, request *management.Pas
|
|||||||
return &empty.Empty{}, err
|
return &empty.Empty{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) SearchUserExternalIDPs(ctx context.Context, request *management.ExternalIDPSearchRequest) (*management.ExternalIDPSearchResponse, error) {
|
||||||
|
externalIDP, err := s.user.SearchExternalIDPs(ctx, externalIDPSearchRequestToModel(request))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return externalIDPSearchResponseFromModel(externalIDP), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RemoveExternalIDP(ctx context.Context, request *management.ExternalIDPRemoveRequest) (*empty.Empty, error) {
|
||||||
|
err := s.user.RemoveExternalIDP(ctx, externalIDPRemoveToModel(request))
|
||||||
|
return &empty.Empty{}, err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) GetUserMfas(ctx context.Context, userID *management.UserID) (*management.MultiFactors, error) {
|
func (s *Server) GetUserMfas(ctx context.Context, userID *management.UserID) (*management.MultiFactors, error) {
|
||||||
mfas, err := s.user.UserMfas(ctx, userID.Id)
|
mfas, err := s.user.UserMfas(ctx, userID.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2,8 +2,8 @@ package management
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/zitadel/internal/model"
|
||||||
"github.com/golang/protobuf/ptypes"
|
"github.com/golang/protobuf/ptypes"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
"google.golang.org/protobuf/encoding/protojson"
|
"google.golang.org/protobuf/encoding/protojson"
|
||||||
@ -66,6 +66,62 @@ func passwordRequestToModel(r *management.PasswordRequest) *usr_model.Password {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func externalIDPSearchRequestToModel(request *management.ExternalIDPSearchRequest) *usr_model.ExternalIDPSearchRequest {
|
||||||
|
return &usr_model.ExternalIDPSearchRequest{
|
||||||
|
Limit: request.Limit,
|
||||||
|
Offset: request.Offset,
|
||||||
|
Queries: []*usr_model.ExternalIDPSearchQuery{{Key: usr_model.ExternalIDPSearchKeyUserID, Method: model.SearchMethodEquals, Value: request.UserId}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func externalIDPRemoveToModel(idp *management.ExternalIDPRemoveRequest) *usr_model.ExternalIDP {
|
||||||
|
return &usr_model.ExternalIDP{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: idp.UserId},
|
||||||
|
IDPConfigID: idp.IdpConfigId,
|
||||||
|
UserID: idp.ExternalUserId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func externalIDPSearchResponseFromModel(response *usr_model.ExternalIDPSearchResponse) *management.ExternalIDPSearchResponse {
|
||||||
|
viewTimestamp, err := ptypes.TimestampProto(response.Timestamp)
|
||||||
|
logging.Log("GRPC-3h8is").OnError(err).Debug("unable to parse timestamp")
|
||||||
|
|
||||||
|
return &management.ExternalIDPSearchResponse{
|
||||||
|
Offset: response.Offset,
|
||||||
|
Limit: response.Limit,
|
||||||
|
TotalResult: response.TotalResult,
|
||||||
|
ProcessedSequence: response.Sequence,
|
||||||
|
ViewTimestamp: viewTimestamp,
|
||||||
|
Result: externalIDPViewsFromModel(response.Result),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func externalIDPViewsFromModel(externalIDPs []*usr_model.ExternalIDPView) []*management.ExternalIDPView {
|
||||||
|
converted := make([]*management.ExternalIDPView, len(externalIDPs))
|
||||||
|
for i, externalIDP := range externalIDPs {
|
||||||
|
converted[i] = externalIDPViewFromModel(externalIDP)
|
||||||
|
}
|
||||||
|
return converted
|
||||||
|
}
|
||||||
|
|
||||||
|
func externalIDPViewFromModel(externalIDP *usr_model.ExternalIDPView) *management.ExternalIDPView {
|
||||||
|
creationDate, err := ptypes.TimestampProto(externalIDP.CreationDate)
|
||||||
|
logging.Log("GRPC-Fdu8s").OnError(err).Debug("unable to parse timestamp")
|
||||||
|
|
||||||
|
changeDate, err := ptypes.TimestampProto(externalIDP.ChangeDate)
|
||||||
|
logging.Log("GRPC-Was7u").OnError(err).Debug("unable to parse timestamp")
|
||||||
|
|
||||||
|
return &management.ExternalIDPView{
|
||||||
|
UserId: externalIDP.UserID,
|
||||||
|
IdpConfigId: externalIDP.IDPConfigID,
|
||||||
|
ExternalUserId: externalIDP.ExternalUserID,
|
||||||
|
ExternalUserDisplayName: externalIDP.UserDisplayName,
|
||||||
|
IdpName: externalIDP.IDPName,
|
||||||
|
CreationDate: creationDate,
|
||||||
|
ChangeDate: changeDate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func userSearchRequestsToModel(project *management.UserSearchRequest) *usr_model.UserSearchRequest {
|
func userSearchRequestsToModel(project *management.UserSearchRequest) *usr_model.UserSearchRequest {
|
||||||
return &usr_model.UserSearchRequest{
|
return &usr_model.UserSearchRequest{
|
||||||
Offset: project.Offset,
|
Offset: project.Offset,
|
||||||
|
@ -2,6 +2,8 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
org_model "github.com/caos/zitadel/internal/org/model"
|
||||||
|
user_model "github.com/caos/zitadel/internal/user/model"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/auth_request/model"
|
"github.com/caos/zitadel/internal/auth_request/model"
|
||||||
)
|
)
|
||||||
@ -14,7 +16,11 @@ type AuthRequestRepository interface {
|
|||||||
SaveAuthCode(ctx context.Context, id, code, userAgentID string) error
|
SaveAuthCode(ctx context.Context, id, code, userAgentID string) error
|
||||||
DeleteAuthRequest(ctx context.Context, id string) error
|
DeleteAuthRequest(ctx context.Context, id string) error
|
||||||
CheckLoginName(ctx context.Context, id, loginName, userAgentID string) error
|
CheckLoginName(ctx context.Context, id, loginName, userAgentID string) error
|
||||||
|
CheckExternalUserLogin(ctx context.Context, authReqID, userAgentID string, user *model.ExternalUser) error
|
||||||
SelectUser(ctx context.Context, id, userID, userAgentID string) error
|
SelectUser(ctx context.Context, id, userID, userAgentID string) error
|
||||||
|
SelectExternalIDP(ctx context.Context, authReqID, idpConfigID, userAgentID string) error
|
||||||
VerifyPassword(ctx context.Context, id, userID, password, userAgentID string, info *model.BrowserInfo) error
|
VerifyPassword(ctx context.Context, id, userID, password, userAgentID string, info *model.BrowserInfo) error
|
||||||
VerifyMfaOTP(ctx context.Context, agentID, authRequestID, code, userAgentID string, info *model.BrowserInfo) error
|
VerifyMfaOTP(ctx context.Context, agentID, authRequestID, code, userAgentID string, info *model.BrowserInfo) error
|
||||||
|
LinkExternalUsers(ctx context.Context, authReqID, userAgentID string) error
|
||||||
|
AutoRegisterExternalUser(ctx context.Context, user *user_model.User, externalIDP *user_model.ExternalIDP, member *org_model.OrgMember, authReqID, userAgentID, resourceOwner string) error
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,12 @@ package eventstore
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/sdk"
|
||||||
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
|
iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||||
|
org_event "github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||||
|
policy_event "github.com/caos/zitadel/internal/policy/repository/eventsourcing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
@ -11,6 +17,7 @@ import (
|
|||||||
cache "github.com/caos/zitadel/internal/auth_request/repository"
|
cache "github.com/caos/zitadel/internal/auth_request/repository"
|
||||||
"github.com/caos/zitadel/internal/errors"
|
"github.com/caos/zitadel/internal/errors"
|
||||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||||
"github.com/caos/zitadel/internal/id"
|
"github.com/caos/zitadel/internal/id"
|
||||||
org_model "github.com/caos/zitadel/internal/org/model"
|
org_model "github.com/caos/zitadel/internal/org/model"
|
||||||
org_view_model "github.com/caos/zitadel/internal/org/repository/view/model"
|
org_view_model "github.com/caos/zitadel/internal/org/repository/view/model"
|
||||||
@ -22,6 +29,8 @@ import (
|
|||||||
|
|
||||||
type AuthRequestRepo struct {
|
type AuthRequestRepo struct {
|
||||||
UserEvents *user_event.UserEventstore
|
UserEvents *user_event.UserEventstore
|
||||||
|
OrgEvents *org_event.OrgEventstore
|
||||||
|
PolicyEvents *policy_event.PolicyEventstore
|
||||||
AuthRequests cache.AuthRequestCache
|
AuthRequests cache.AuthRequestCache
|
||||||
View *view.View
|
View *view.View
|
||||||
|
|
||||||
@ -29,6 +38,8 @@ type AuthRequestRepo struct {
|
|||||||
UserViewProvider userViewProvider
|
UserViewProvider userViewProvider
|
||||||
UserEventProvider userEventProvider
|
UserEventProvider userEventProvider
|
||||||
OrgViewProvider orgViewProvider
|
OrgViewProvider orgViewProvider
|
||||||
|
LoginPolicyViewProvider loginPolicyViewProvider
|
||||||
|
IDPProviderViewProvider idpProviderViewProvider
|
||||||
|
|
||||||
IdGenerator id.Generator
|
IdGenerator id.Generator
|
||||||
|
|
||||||
@ -36,6 +47,8 @@ type AuthRequestRepo struct {
|
|||||||
MfaInitSkippedLifeTime time.Duration
|
MfaInitSkippedLifeTime time.Duration
|
||||||
MfaSoftwareCheckLifeTime time.Duration
|
MfaSoftwareCheckLifeTime time.Duration
|
||||||
MfaHardwareCheckLifeTime time.Duration
|
MfaHardwareCheckLifeTime time.Duration
|
||||||
|
|
||||||
|
IAMID string
|
||||||
}
|
}
|
||||||
|
|
||||||
type userSessionViewProvider interface {
|
type userSessionViewProvider interface {
|
||||||
@ -46,8 +59,17 @@ type userViewProvider interface {
|
|||||||
UserByID(string) (*user_view_model.UserView, error)
|
UserByID(string) (*user_view_model.UserView, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type loginPolicyViewProvider interface {
|
||||||
|
LoginPolicyByAggregateID(string) (*iam_view_model.LoginPolicyView, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type idpProviderViewProvider interface {
|
||||||
|
IDPProvidersByAggregateID(string) ([]*iam_view_model.IDPProviderView, error)
|
||||||
|
}
|
||||||
|
|
||||||
type userEventProvider interface {
|
type userEventProvider interface {
|
||||||
UserEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error)
|
UserEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error)
|
||||||
|
BulkAddExternalIDPs(ctx context.Context, userID string, externalIDPs []*user_model.ExternalIDP) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type orgViewProvider interface {
|
type orgViewProvider interface {
|
||||||
@ -73,7 +95,7 @@ func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *mod
|
|||||||
}
|
}
|
||||||
request.Audience = ids
|
request.Audience = ids
|
||||||
if request.LoginHint != "" {
|
if request.LoginHint != "" {
|
||||||
err = repo.checkLoginName(request, request.LoginHint)
|
err = repo.checkLoginName(ctx, request, request.LoginHint)
|
||||||
logging.LogWithFields("EVENT-aG311", "login name", request.LoginHint, "id", request.ID, "applicationID", request.ApplicationID).Debug("login hint invalid")
|
logging.LogWithFields("EVENT-aG311", "login name", request.LoginHint, "id", request.ID, "applicationID", request.ApplicationID).Debug("login hint invalid")
|
||||||
}
|
}
|
||||||
err = repo.AuthRequests.SaveAuthRequest(ctx, request)
|
err = repo.AuthRequests.SaveAuthRequest(ctx, request)
|
||||||
@ -122,13 +144,45 @@ func (repo *AuthRequestRepo) CheckLoginName(ctx context.Context, id, loginName,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = repo.checkLoginName(request, loginName)
|
err = repo.checkLoginName(ctx, request, loginName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
|
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) SelectExternalIDP(ctx context.Context, authReqID, idpConfigID, userAgentID string) error {
|
||||||
|
request, err := repo.getAuthRequest(ctx, authReqID, userAgentID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = repo.checkSelectedExternalIDP(request, idpConfigID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) CheckExternalUserLogin(ctx context.Context, authReqID, userAgentID string, externalUser *model.ExternalUser) error {
|
||||||
|
request, err := repo.getAuthRequest(ctx, authReqID, userAgentID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = repo.checkExternalUserLogin(request, externalUser.IDPConfigID, externalUser.ExternalUserID)
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
return repo.setLinkingUser(ctx, request, externalUser)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) setLinkingUser(ctx context.Context, request *model.AuthRequest, externalUser *model.ExternalUser) error {
|
||||||
|
request.LinkingUsers = append(request.LinkingUsers, externalUser)
|
||||||
|
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID, userAgentID string) error {
|
func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID, userAgentID string) error {
|
||||||
request, err := repo.getAuthRequest(ctx, id, userAgentID)
|
request, err := repo.getAuthRequest(ctx, id, userAgentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -164,6 +218,59 @@ func (repo *AuthRequestRepo) VerifyMfaOTP(ctx context.Context, authRequestID, us
|
|||||||
return repo.UserEvents.CheckMfaOTP(ctx, userID, code, request.WithCurrentInfo(info))
|
return repo.UserEvents.CheckMfaOTP(ctx, userID, code, request.WithCurrentInfo(info))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) LinkExternalUsers(ctx context.Context, authReqID, userAgentID string) error {
|
||||||
|
request, err := repo.getAuthRequest(ctx, authReqID, userAgentID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = linkExternalIDPs(ctx, repo.UserEventProvider, request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
request.LinkingUsers = nil
|
||||||
|
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, registerUser *user_model.User, externalIDP *user_model.ExternalIDP, orgMember *org_model.OrgMember, authReqID, userAgentID, resourceOwner string) error {
|
||||||
|
request, err := repo.getAuthRequest(ctx, authReqID, userAgentID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
policyResourceOwner := authz.GetCtxData(ctx).OrgID
|
||||||
|
if resourceOwner != "" {
|
||||||
|
policyResourceOwner = resourceOwner
|
||||||
|
}
|
||||||
|
pwPolicy, err := repo.PolicyEvents.GetPasswordComplexityPolicy(ctx, policyResourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
orgPolicy, err := repo.OrgEvents.GetOrgIAMPolicy(ctx, policyResourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user, aggregates, err := repo.UserEvents.PrepareRegisterUser(ctx, registerUser, externalIDP, pwPolicy, orgPolicy, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if orgMember != nil {
|
||||||
|
orgMember.UserID = user.AggregateID
|
||||||
|
_, memberAggregate, err := repo.OrgEvents.PrepareAddOrgMember(ctx, orgMember, policyResourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
aggregates = append(aggregates, memberAggregate)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sdk.PushAggregates(ctx, repo.UserEvents.PushAggregates, user.AppendEvents, aggregates...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
request.UserID = user.AggregateID
|
||||||
|
request.SelectedIDPConfigID = externalIDP.IDPConfigID
|
||||||
|
request.LinkingUsers = nil
|
||||||
|
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *AuthRequestRepo) getAuthRequestNextSteps(ctx context.Context, id, userAgentID string, checkLoggedIn bool) (*model.AuthRequest, error) {
|
func (repo *AuthRequestRepo) getAuthRequestNextSteps(ctx context.Context, id, userAgentID string, checkLoggedIn bool) (*model.AuthRequest, error) {
|
||||||
request, err := repo.getAuthRequest(ctx, id, userAgentID)
|
request, err := repo.getAuthRequest(ctx, id, userAgentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -185,18 +292,114 @@ func (repo *AuthRequestRepo) getAuthRequest(ctx context.Context, id, userAgentID
|
|||||||
if request.AgentID != userAgentID {
|
if request.AgentID != userAgentID {
|
||||||
return nil, errors.ThrowPermissionDenied(nil, "EVENT-adk13", "Errors.AuthRequest.UserAgentNotCorresponding")
|
return nil, errors.ThrowPermissionDenied(nil, "EVENT-adk13", "Errors.AuthRequest.UserAgentNotCorresponding")
|
||||||
}
|
}
|
||||||
|
err = repo.fillLoginPolicy(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return request, nil
|
return request, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *AuthRequestRepo) checkLoginName(request *model.AuthRequest, loginName string) error {
|
func (repo *AuthRequestRepo) getLoginPolicyAndIDPProviders(ctx context.Context, orgID string) (*iam_model.LoginPolicyView, []*iam_model.IDPProviderView, error) {
|
||||||
user, err := repo.View.UserByLoginName(loginName)
|
policy, err := repo.getLoginPolicy(ctx, orgID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if !policy.AllowExternalIDP {
|
||||||
|
return policy, nil, nil
|
||||||
|
}
|
||||||
|
idpProviders, err := getLoginPolicyIDPProviders(repo.IDPProviderViewProvider, repo.IAMID, orgID, policy.Default)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return policy, idpProviders, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) fillLoginPolicy(ctx context.Context, request *model.AuthRequest) error {
|
||||||
|
orgID := request.UserOrgID
|
||||||
|
if orgID == "" {
|
||||||
|
orgID = request.GetScopeOrgID()
|
||||||
|
}
|
||||||
|
if orgID == "" {
|
||||||
|
orgID = repo.IAMID
|
||||||
|
}
|
||||||
|
|
||||||
|
policy, idpProviders, err := repo.getLoginPolicyAndIDPProviders(ctx, orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
request.LoginPolicy = policy
|
||||||
|
if idpProviders != nil {
|
||||||
|
request.AllowedExternalIDPs = idpProviders
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *model.AuthRequest, loginName string) (err error) {
|
||||||
|
orgID := request.GetScopeOrgID()
|
||||||
|
user := new(user_view_model.UserView)
|
||||||
|
if orgID != "" {
|
||||||
|
user, err = repo.View.UserByLoginNameAndResourceOwner(loginName, orgID)
|
||||||
|
} else {
|
||||||
|
user, err = repo.View.UserByLoginName(loginName)
|
||||||
|
if err == nil {
|
||||||
|
err = repo.checkLoginPolicyWithResourceOwner(ctx, request, user)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
request.SetUserInfo(user.ID, loginName, "", user.ResourceOwner)
|
request.SetUserInfo(user.ID, loginName, "", user.ResourceOwner)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo AuthRequestRepo) checkLoginPolicyWithResourceOwner(ctx context.Context, request *model.AuthRequest, user *user_view_model.UserView) error {
|
||||||
|
loginPolicy, idpProviders, err := repo.getLoginPolicyAndIDPProviders(ctx, user.ResourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(request.LinkingUsers) != 0 && !loginPolicy.AllowExternalIDP {
|
||||||
|
return errors.ThrowInvalidArgument(nil, "LOGIN-s9sio", "Errors.User.NotAllowedToLink")
|
||||||
|
}
|
||||||
|
if len(request.LinkingUsers) != 0 {
|
||||||
|
exists := linkingIDPConfigExistingInAllowedIDPs(request.LinkingUsers, idpProviders)
|
||||||
|
if !exists {
|
||||||
|
return errors.ThrowInvalidArgument(nil, "LOGIN-Dj89o", "Errors.User.NotAllowedToLink")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.LoginPolicy = loginPolicy
|
||||||
|
request.AllowedExternalIDPs = idpProviders
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) checkSelectedExternalIDP(request *model.AuthRequest, idpConfigID string) error {
|
||||||
|
for _, externalIDP := range request.AllowedExternalIDPs {
|
||||||
|
if externalIDP.IDPConfigID == idpConfigID {
|
||||||
|
request.SelectedIDPConfigID = idpConfigID
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.ThrowNotFound(nil, "LOGIN-Nsm8r", "Errors.User.ExternalIDP.NotAllowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) checkExternalUserLogin(request *model.AuthRequest, idpConfigID, externalUserID string) (err error) {
|
||||||
|
orgID := request.GetScopeOrgID()
|
||||||
|
externalIDP := new(user_view_model.ExternalIDPView)
|
||||||
|
if orgID != "" {
|
||||||
|
externalIDP, err = repo.View.ExternalIDPByExternalUserIDAndIDPConfigIDAndResourceOwner(externalUserID, idpConfigID, orgID)
|
||||||
|
} else {
|
||||||
|
externalIDP, err = repo.View.ExternalIDPByExternalUserIDAndIDPConfigID(externalUserID, idpConfigID)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
request.SetUserInfo(externalIDP.UserID, "", "", externalIDP.ResourceOwner)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthRequest, checkLoggedIn bool) ([]model.NextStep, error) {
|
func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthRequest, checkLoggedIn bool) ([]model.NextStep, error) {
|
||||||
if request == nil {
|
if request == nil {
|
||||||
return nil, errors.ThrowInvalidArgument(nil, "EVENT-ds27a", "Errors.Internal")
|
return nil, errors.ThrowInvalidArgument(nil, "EVENT-ds27a", "Errors.Internal")
|
||||||
@ -206,7 +409,11 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthR
|
|||||||
return append(steps, &model.RedirectToCallbackStep{}), nil
|
return append(steps, &model.RedirectToCallbackStep{}), nil
|
||||||
}
|
}
|
||||||
if request.UserID == "" {
|
if request.UserID == "" {
|
||||||
steps = append(steps, &model.LoginStep{})
|
if request.LinkingUsers != nil && len(request.LinkingUsers) > 0 {
|
||||||
|
steps = append(steps, new(model.ExternalNotFoundOptionStep))
|
||||||
|
return steps, nil
|
||||||
|
}
|
||||||
|
steps = append(steps, new(model.LoginStep))
|
||||||
if request.Prompt == model.PromptSelectAccount || request.Prompt == model.PromptUnspecified {
|
if request.Prompt == model.PromptSelectAccount || request.Prompt == model.PromptUnspecified {
|
||||||
users, err := repo.usersForUserSelection(request)
|
users, err := repo.usersForUserSelection(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -222,23 +429,26 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthR
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
request.LoginName = user.PreferredLoginName
|
||||||
userSession, err := userSessionByIDs(ctx, repo.UserSessionViewProvider, repo.UserEventProvider, request.AgentID, user)
|
userSession, err := userSessionByIDs(ctx, repo.UserSessionViewProvider, repo.UserEventProvider, request.AgentID, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.InitRequired {
|
if request.SelectedIDPConfigID == "" {
|
||||||
return append(steps, &model.InitUserStep{PasswordSet: user.PasswordSet}), nil
|
if user.InitRequired {
|
||||||
}
|
return append(steps, &model.InitUserStep{PasswordSet: user.PasswordSet}), nil
|
||||||
if !user.PasswordSet {
|
}
|
||||||
return append(steps, &model.InitPasswordStep{}), nil
|
if !user.PasswordSet {
|
||||||
}
|
return append(steps, &model.InitPasswordStep{}), nil
|
||||||
|
}
|
||||||
|
|
||||||
if !checkVerificationTime(userSession.PasswordVerification, repo.PasswordCheckLifeTime) {
|
if !checkVerificationTime(userSession.PasswordVerification, repo.PasswordCheckLifeTime) {
|
||||||
return append(steps, &model.PasswordStep{}), nil
|
return append(steps, &model.PasswordStep{}), nil
|
||||||
|
}
|
||||||
|
request.PasswordVerified = true
|
||||||
|
request.AuthTime = userSession.PasswordVerification
|
||||||
}
|
}
|
||||||
request.PasswordVerified = true
|
|
||||||
request.AuthTime = userSession.PasswordVerification
|
|
||||||
|
|
||||||
if step, ok := repo.mfaChecked(userSession, request, user); !ok {
|
if step, ok := repo.mfaChecked(userSession, request, user); !ok {
|
||||||
return append(steps, step), nil
|
return append(steps, step), nil
|
||||||
@ -258,6 +468,10 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthR
|
|||||||
return steps, nil
|
return steps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if request.LinkingUsers != nil && len(request.LinkingUsers) != 0 {
|
||||||
|
return append(steps, &model.LinkUsersStep{}), nil
|
||||||
|
|
||||||
|
}
|
||||||
//PLANNED: consent step
|
//PLANNED: consent step
|
||||||
return append(steps, &model.RedirectToCallbackStep{}), nil
|
return append(steps, &model.RedirectToCallbackStep{}), nil
|
||||||
}
|
}
|
||||||
@ -322,6 +536,36 @@ func (repo *AuthRequestRepo) mfaSkippedOrSetUp(user *user_model.UserView) bool {
|
|||||||
return checkVerificationTime(user.MfaInitSkipped, repo.MfaInitSkippedLifeTime)
|
return checkVerificationTime(user.MfaInitSkipped, repo.MfaInitSkippedLifeTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) getLoginPolicy(ctx context.Context, orgID string) (*iam_model.LoginPolicyView, error) {
|
||||||
|
policy, err := repo.View.LoginPolicyByAggregateID(orgID)
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
policy, err = repo.View.LoginPolicyByAggregateID(repo.IAMID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
policy.Default = true
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return iam_es_model.LoginPolicyViewToModel(policy), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLoginPolicyIDPProviders(provider idpProviderViewProvider, iamID, orgID string, defaultPolicy bool) ([]*iam_model.IDPProviderView, error) {
|
||||||
|
if defaultPolicy {
|
||||||
|
idpProviders, err := provider.IDPProvidersByAggregateID(iamID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return iam_es_model.IDPProviderViewsToModel(idpProviders), nil
|
||||||
|
}
|
||||||
|
idpProviders, err := provider.IDPProvidersByAggregateID(orgID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return iam_es_model.IDPProviderViewsToModel(idpProviders), nil
|
||||||
|
}
|
||||||
|
|
||||||
func checkVerificationTime(verificationTime time.Time, lifetime time.Duration) bool {
|
func checkVerificationTime(verificationTime time.Time, lifetime time.Duration) bool {
|
||||||
return verificationTime.Add(lifetime).After(time.Now().UTC())
|
return verificationTime.Add(lifetime).After(time.Now().UTC())
|
||||||
}
|
}
|
||||||
@ -422,3 +666,37 @@ func userByID(ctx context.Context, viewProvider userViewProvider, eventProvider
|
|||||||
}
|
}
|
||||||
return user_view_model.UserToModel(&userCopy), nil
|
return user_view_model.UserToModel(&userCopy), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func linkExternalIDPs(ctx context.Context, userEventProvider userEventProvider, request *model.AuthRequest) error {
|
||||||
|
externalIDPs := make([]*user_model.ExternalIDP, len(request.LinkingUsers))
|
||||||
|
for i, linkingUser := range request.LinkingUsers {
|
||||||
|
externalIDP := &user_model.ExternalIDP{
|
||||||
|
ObjectRoot: es_models.ObjectRoot{AggregateID: request.UserID},
|
||||||
|
IDPConfigID: linkingUser.IDPConfigID,
|
||||||
|
UserID: linkingUser.ExternalUserID,
|
||||||
|
DisplayName: linkingUser.DisplayName,
|
||||||
|
}
|
||||||
|
externalIDPs[i] = externalIDP
|
||||||
|
}
|
||||||
|
data := authz.CtxData{
|
||||||
|
UserID: "LOGIN",
|
||||||
|
OrgID: request.UserOrgID,
|
||||||
|
}
|
||||||
|
return userEventProvider.BulkAddExternalIDPs(authz.SetCtxData(ctx, data), request.UserID, externalIDPs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func linkingIDPConfigExistingInAllowedIDPs(linkingUsers []*model.ExternalUser, idpProviders []*iam_model.IDPProviderView) bool {
|
||||||
|
for _, linkingUser := range linkingUsers {
|
||||||
|
exists := false
|
||||||
|
for _, idp := range idpProviders {
|
||||||
|
if idp.IDPConfigID == linkingUser.IDPConfigID {
|
||||||
|
exists = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
@ -88,12 +88,20 @@ func (m *mockEventUser) UserEventsByID(ctx context.Context, id string, sequence
|
|||||||
return events, nil
|
return events, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockEventUser) BulkAddExternalIDPs(ctx context.Context, userID string, externalIDPs []*user_model.ExternalIDP) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type mockEventErrUser struct{}
|
type mockEventErrUser struct{}
|
||||||
|
|
||||||
func (m *mockEventErrUser) UserEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error) {
|
func (m *mockEventErrUser) UserEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error) {
|
||||||
return nil, errors.ThrowInternal(nil, "id", "internal error")
|
return nil, errors.ThrowInternal(nil, "id", "internal error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockEventErrUser) BulkAddExternalIDPs(ctx context.Context, userID string, externalIDPs []*user_model.ExternalIDP) error {
|
||||||
|
return errors.ThrowInternal(nil, "id", "internal error")
|
||||||
|
}
|
||||||
|
|
||||||
type mockViewUser struct {
|
type mockViewUser struct {
|
||||||
InitRequired bool
|
InitRequired bool
|
||||||
PasswordSet bool
|
PasswordSet bool
|
||||||
@ -185,6 +193,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
[]model.NextStep{&model.LoginStep{}},
|
[]model.NextStep{&model.LoginStep{}},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"user not set no active session, linking users, external user not found option",
|
||||||
|
fields{
|
||||||
|
userSessionViewProvider: &mockViewNoUserSession{},
|
||||||
|
},
|
||||||
|
args{&model.AuthRequest{LinkingUsers: []*model.ExternalUser{{IDPConfigID: "IDPConfigID", ExternalUserID: "ExternalUserID"}}}, false},
|
||||||
|
[]model.NextStep{&model.ExternalNotFoundOptionStep{}},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"user not set, prompt select account and internal error, internal error",
|
"user not set, prompt select account and internal error, internal error",
|
||||||
fields{
|
fields{
|
||||||
@ -363,6 +380,24 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
[]model.NextStep{&model.InitPasswordStep{}},
|
[]model.NextStep{&model.InitPasswordStep{}},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"external user (no password set), callback",
|
||||||
|
fields{
|
||||||
|
userSessionViewProvider: &mockViewUserSession{
|
||||||
|
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
|
||||||
|
},
|
||||||
|
userViewProvider: &mockViewUser{
|
||||||
|
IsEmailVerified: true,
|
||||||
|
MfaMaxSetUp: int32(model.MfaLevelSoftware),
|
||||||
|
},
|
||||||
|
userEventProvider: &mockEventUser{},
|
||||||
|
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
|
||||||
|
MfaSoftwareCheckLifeTime: 18 * time.Hour,
|
||||||
|
},
|
||||||
|
args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID"}, false},
|
||||||
|
[]model.NextStep{&model.RedirectToCallbackStep{}},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"password not verified, password check step",
|
"password not verified, password check step",
|
||||||
fields{
|
fields{
|
||||||
@ -378,6 +413,25 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
[]model.NextStep{&model.PasswordStep{}},
|
[]model.NextStep{&model.PasswordStep{}},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"external user (no password check needed), callback",
|
||||||
|
fields{
|
||||||
|
userSessionViewProvider: &mockViewUserSession{
|
||||||
|
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
|
||||||
|
},
|
||||||
|
userViewProvider: &mockViewUser{
|
||||||
|
PasswordSet: true,
|
||||||
|
IsEmailVerified: true,
|
||||||
|
MfaMaxSetUp: int32(model.MfaLevelSoftware),
|
||||||
|
},
|
||||||
|
userEventProvider: &mockEventUser{},
|
||||||
|
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
|
||||||
|
MfaSoftwareCheckLifeTime: 18 * time.Hour,
|
||||||
|
},
|
||||||
|
args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID"}, false},
|
||||||
|
[]model.NextStep{&model.RedirectToCallbackStep{}},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"mfa not verified, mfa check step",
|
"mfa not verified, mfa check step",
|
||||||
fields{
|
fields{
|
||||||
@ -400,6 +454,28 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
}},
|
}},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"external user, mfa not verified, mfa check step",
|
||||||
|
fields{
|
||||||
|
userSessionViewProvider: &mockViewUserSession{
|
||||||
|
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
|
||||||
|
},
|
||||||
|
userViewProvider: &mockViewUser{
|
||||||
|
PasswordSet: true,
|
||||||
|
OTPState: int32(user_model.MfaStateReady),
|
||||||
|
MfaMaxSetUp: int32(model.MfaLevelSoftware),
|
||||||
|
},
|
||||||
|
userEventProvider: &mockEventUser{},
|
||||||
|
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
|
||||||
|
PasswordCheckLifeTime: 10 * 24 * time.Hour,
|
||||||
|
MfaSoftwareCheckLifeTime: 18 * time.Hour,
|
||||||
|
},
|
||||||
|
args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID"}, false},
|
||||||
|
[]model.NextStep{&model.MfaVerificationStep{
|
||||||
|
MfaProviders: []model.MfaType{model.MfaTypeOTP},
|
||||||
|
}},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"password change required and email verified, password change step",
|
"password change required and email verified, password change step",
|
||||||
fields{
|
fields{
|
||||||
@ -505,6 +581,30 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
[]model.NextStep{&model.RedirectToCallbackStep{}},
|
[]model.NextStep{&model.RedirectToCallbackStep{}},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"linking users, link users step",
|
||||||
|
fields{
|
||||||
|
userSessionViewProvider: &mockViewUserSession{
|
||||||
|
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
|
||||||
|
},
|
||||||
|
userViewProvider: &mockViewUser{
|
||||||
|
PasswordSet: true,
|
||||||
|
IsEmailVerified: true,
|
||||||
|
MfaMaxSetUp: int32(model.MfaLevelSoftware),
|
||||||
|
},
|
||||||
|
userEventProvider: &mockEventUser{},
|
||||||
|
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
|
||||||
|
MfaSoftwareCheckLifeTime: 18 * time.Hour,
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
&model.AuthRequest{
|
||||||
|
UserID: "UserID",
|
||||||
|
SelectedIDPConfigID: "IDPConfigID",
|
||||||
|
LinkingUsers: []*model.ExternalUser{{IDPConfigID: "IDPConfigID", ExternalUserID: "UserID", DisplayName: "DisplayName"}},
|
||||||
|
}, false},
|
||||||
|
[]model.NextStep{&model.LinkUsersStep{}},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -2,8 +2,9 @@ package eventstore
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
|
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||||
|
|
||||||
auth_model "github.com/caos/zitadel/internal/auth/model"
|
auth_model "github.com/caos/zitadel/internal/auth/model"
|
||||||
auth_view "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
auth_view "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||||
@ -41,7 +42,7 @@ func (repo *OrgRepository) SearchOrgs(ctx context.Context, request *org_model.Or
|
|||||||
result := &org_model.OrgSearchResult{
|
result := &org_model.OrgSearchResult{
|
||||||
Offset: request.Offset,
|
Offset: request.Offset,
|
||||||
Limit: request.Limit,
|
Limit: request.Limit,
|
||||||
TotalResult: uint64(count),
|
TotalResult: count,
|
||||||
Result: model.OrgsToModel(members),
|
Result: model.OrgsToModel(members),
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -71,7 +72,7 @@ func (repo *OrgRepository) RegisterOrg(ctx context.Context, register *auth_model
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
user, userAggregates, err := repo.UserEventstore.PrepareRegisterUser(ctx, register.User, pwPolicy, orgPolicy, org.AggregateID)
|
user, userAggregates, err := repo.UserEventstore.PrepareRegisterUser(ctx, register.User, nil, pwPolicy, orgPolicy, org.AggregateID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -94,6 +95,18 @@ func (repo *OrgRepository) RegisterOrg(ctx context.Context, register *auth_model
|
|||||||
return RegisterToModel(registerModel), nil
|
return RegisterToModel(registerModel), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *OrgRepository) GetOrgIamPolicy(ctx context.Context, orgID string) (*org_model.OrgIAMPolicy, error) {
|
func (repo *OrgRepository) GetDefaultOrgIamPolicy(ctx context.Context) *org_model.OrgIAMPolicy {
|
||||||
return repo.OrgEventstore.GetOrgIAMPolicy(ctx, policy_model.DefaultPolicy)
|
return repo.OrgEventstore.GetDefaultOrgIAMPolicy(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *OrgRepository) GetOrgIamPolicy(ctx context.Context, orgID string) (*org_model.OrgIAMPolicy, error) {
|
||||||
|
return repo.OrgEventstore.GetOrgIAMPolicy(ctx, orgID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *OrgRepository) GetIDPConfigByID(ctx context.Context, idpConfigID string) (*iam_model.IDPConfigView, error) {
|
||||||
|
idpConfig, err := repo.View.IDPConfigByID(idpConfigID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return iam_view_model.IDPConfigViewToModel(idpConfig), nil
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UserRepo struct {
|
type UserRepo struct {
|
||||||
|
SearchLimit uint64
|
||||||
Eventstore eventstore.Eventstore
|
Eventstore eventstore.Eventstore
|
||||||
UserEvents *user_event.UserEventstore
|
UserEvents *user_event.UserEventstore
|
||||||
OrgEvents *org_event.OrgEventstore
|
OrgEvents *org_event.OrgEventstore
|
||||||
@ -32,7 +33,15 @@ func (repo *UserRepo) Health(ctx context.Context) error {
|
|||||||
return repo.UserEvents.Health(ctx)
|
return repo.UserEvents.Health(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *UserRepo) Register(ctx context.Context, registerUser *model.User, orgMember *org_model.OrgMember, resourceOwner string) (*model.User, error) {
|
func (repo *UserRepo) Register(ctx context.Context, user *model.User, orgMember *org_model.OrgMember, resourceOwner string) (*model.User, error) {
|
||||||
|
return repo.registerUser(ctx, user, nil, orgMember, resourceOwner)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) RegisterExternalUser(ctx context.Context, user *model.User, externalIDP *model.ExternalIDP, orgMember *org_model.OrgMember, resourceOwner string) (*model.User, error) {
|
||||||
|
return repo.registerUser(ctx, user, externalIDP, orgMember, resourceOwner)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) registerUser(ctx context.Context, registerUser *model.User, externalIDP *model.ExternalIDP, orgMember *org_model.OrgMember, resourceOwner string) (*model.User, error) {
|
||||||
policyResourceOwner := authz.GetCtxData(ctx).OrgID
|
policyResourceOwner := authz.GetCtxData(ctx).OrgID
|
||||||
if resourceOwner != "" {
|
if resourceOwner != "" {
|
||||||
policyResourceOwner = resourceOwner
|
policyResourceOwner = resourceOwner
|
||||||
@ -45,7 +54,7 @@ func (repo *UserRepo) Register(ctx context.Context, registerUser *model.User, or
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
user, aggregates, err := repo.UserEvents.PrepareRegisterUser(ctx, registerUser, pwPolicy, orgPolicy, resourceOwner)
|
user, aggregates, err := repo.UserEvents.PrepareRegisterUser(ctx, registerUser, externalIDP, pwPolicy, orgPolicy, resourceOwner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -87,6 +96,42 @@ func (repo *UserRepo) ChangeMyProfile(ctx context.Context, profile *model.Profil
|
|||||||
return repo.UserEvents.ChangeProfile(ctx, profile)
|
return repo.UserEvents.ChangeProfile(ctx, profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) SearchMyExternalIDPs(ctx context.Context, request *model.ExternalIDPSearchRequest) (*model.ExternalIDPSearchResponse, error) {
|
||||||
|
request.EnsureLimit(repo.SearchLimit)
|
||||||
|
sequence, seqErr := repo.View.GetLatestExternalIDPSequence()
|
||||||
|
logging.Log("EVENT-5Jsi8").OnError(seqErr).Warn("could not read latest user sequence")
|
||||||
|
request.AppendUserQuery(authz.GetCtxData(ctx).UserID)
|
||||||
|
externalIDPS, count, err := repo.View.SearchExternalIDPs(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &model.ExternalIDPSearchResponse{
|
||||||
|
Offset: request.Offset,
|
||||||
|
Limit: request.Limit,
|
||||||
|
TotalResult: count,
|
||||||
|
Result: usr_view_model.ExternalIDPViewsToModel(externalIDPS),
|
||||||
|
}
|
||||||
|
if seqErr == nil {
|
||||||
|
result.Sequence = sequence.CurrentSequence
|
||||||
|
result.Timestamp = sequence.CurrentTimestamp
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) AddMyExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) (*model.ExternalIDP, error) {
|
||||||
|
if err := checkIDs(ctx, externalIDP.ObjectRoot); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return repo.UserEvents.AddExternalIDP(ctx, externalIDP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) RemoveMyExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) error {
|
||||||
|
if err := checkIDs(ctx, externalIDP.ObjectRoot); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return repo.UserEvents.RemoveExternalIDP(ctx, externalIDP)
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *UserRepo) MyEmail(ctx context.Context) (*model.Email, error) {
|
func (repo *UserRepo) MyEmail(ctx context.Context) (*model.Email, error) {
|
||||||
user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID)
|
user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -53,6 +53,10 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, ev
|
|||||||
iamEvents: repos.IamEvents,
|
iamEvents: repos.IamEvents,
|
||||||
iamID: systemDefaults.IamID},
|
iamID: systemDefaults.IamID},
|
||||||
&MachineKeys{handler: handler{view, bulkLimit, configs.cycleDuration("MachineKey"), errorCount}},
|
&MachineKeys{handler: handler{view, bulkLimit, configs.cycleDuration("MachineKey"), errorCount}},
|
||||||
|
&LoginPolicy{handler: handler{view, bulkLimit, configs.cycleDuration("LoginPolicy"), errorCount}},
|
||||||
|
&IDPConfig{handler: handler{view, bulkLimit, configs.cycleDuration("IDPConfig"), errorCount}},
|
||||||
|
&IDPProvider{handler: handler{view, bulkLimit, configs.cycleDuration("IDPProvider"), errorCount}, systemDefaults: systemDefaults, orgEvents: repos.OrgEvents, iamEvents: repos.IamEvents},
|
||||||
|
&ExternalIDP{handler: handler{view, bulkLimit, configs.cycleDuration("ExternalIDP"), errorCount}, systemDefaults: systemDefaults, orgEvents: repos.OrgEvents, iamEvents: repos.IamEvents},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
82
internal/auth/repository/eventsourcing/handler/idp_config.go
Normal file
82
internal/auth/repository/eventsourcing/handler/idp_config.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||||
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
|
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||||
|
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||||
|
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IDPConfig struct {
|
||||||
|
handler
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
idpConfigTable = "auth.idp_configs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *IDPConfig) ViewModel() string {
|
||||||
|
return idpConfigTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *IDPConfig) EventQuery() (*models.SearchQuery, error) {
|
||||||
|
sequence, err := m.view.GetLatestIDPConfigSequence()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return es_models.NewSearchQuery().
|
||||||
|
AggregateTypeFilter(model.OrgAggregate, iam_es_model.IAMAggregate).
|
||||||
|
LatestSequenceFilter(sequence.CurrentSequence), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *IDPConfig) Reduce(event *models.Event) (err error) {
|
||||||
|
switch event.AggregateType {
|
||||||
|
case model.OrgAggregate:
|
||||||
|
err = m.processIdpConfig(iam_model.IDPProviderTypeOrg, event)
|
||||||
|
case iam_es_model.IAMAggregate:
|
||||||
|
err = m.processIdpConfig(iam_model.IDPProviderTypeSystem, event)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *IDPConfig) processIdpConfig(providerType iam_model.IDPProviderType, event *models.Event) (err error) {
|
||||||
|
idp := new(iam_view_model.IDPConfigView)
|
||||||
|
switch event.Type {
|
||||||
|
case model.IDPConfigAdded,
|
||||||
|
iam_es_model.IDPConfigAdded:
|
||||||
|
err = idp.AppendEvent(providerType, event)
|
||||||
|
case model.IDPConfigChanged, iam_es_model.IDPConfigChanged,
|
||||||
|
model.OIDCIDPConfigAdded, iam_es_model.OIDCIDPConfigAdded,
|
||||||
|
model.OIDCIDPConfigChanged, iam_es_model.OIDCIDPConfigChanged:
|
||||||
|
err = idp.SetData(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
idp, err = m.view.IDPConfigByID(idp.IDPConfigID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = idp.AppendEvent(providerType, event)
|
||||||
|
case model.IDPConfigRemoved, iam_es_model.IDPConfigRemoved:
|
||||||
|
err = idp.SetData(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return m.view.DeleteIDPConfig(idp.IDPConfigID, event.Sequence)
|
||||||
|
default:
|
||||||
|
return m.view.ProcessedIDPConfigSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return m.view.PutIDPConfig(idp, idp.Sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *IDPConfig) OnError(event *models.Event, err error) error {
|
||||||
|
logging.LogWithFields("SPOOL-Ejf8s", "id", event.AggregateID).WithError(err).Warn("something went wrong in idp config handler")
|
||||||
|
return spooler.HandleError(event, err, m.view.GetLatestIDPConfigFailedEvent, m.view.ProcessedIDPConfigFailedEvent, m.view.ProcessedIDPConfigSequence, m.errorCountUntilSkip)
|
||||||
|
}
|
120
internal/auth/repository/eventsourcing/handler/idp_providers.go
Normal file
120
internal/auth/repository/eventsourcing/handler/idp_providers.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
|
"github.com/caos/zitadel/internal/iam/repository/eventsourcing"
|
||||||
|
org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||||
|
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||||
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
|
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||||
|
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IDPProvider struct {
|
||||||
|
handler
|
||||||
|
systemDefaults systemdefaults.SystemDefaults
|
||||||
|
iamEvents *eventsourcing.IAMEventstore
|
||||||
|
orgEvents *org_es.OrgEventstore
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
idpProviderTable = "auth.idp_providers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *IDPProvider) ViewModel() string {
|
||||||
|
return idpProviderTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *IDPProvider) EventQuery() (*models.SearchQuery, error) {
|
||||||
|
sequence, err := m.view.GetLatestIDPProviderSequence()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return es_models.NewSearchQuery().
|
||||||
|
AggregateTypeFilter(model.IAMAggregate, org_es_model.OrgAggregate).
|
||||||
|
LatestSequenceFilter(sequence.CurrentSequence), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *IDPProvider) Reduce(event *models.Event) (err error) {
|
||||||
|
switch event.AggregateType {
|
||||||
|
case model.IAMAggregate, org_es_model.OrgAggregate:
|
||||||
|
err = m.processIdpProvider(event)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *IDPProvider) processIdpProvider(event *models.Event) (err error) {
|
||||||
|
provider := new(iam_view_model.IDPProviderView)
|
||||||
|
switch event.Type {
|
||||||
|
case model.LoginPolicyIDPProviderAdded, org_es_model.LoginPolicyIDPProviderAdded:
|
||||||
|
err = provider.AppendEvent(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = m.fillData(provider)
|
||||||
|
case model.LoginPolicyIDPProviderRemoved, model.LoginPolicyIDPProviderCascadeRemoved,
|
||||||
|
org_es_model.LoginPolicyIDPProviderRemoved, org_es_model.LoginPolicyIDPProviderCascadeRemoved:
|
||||||
|
err = provider.SetData(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return m.view.DeleteIDPProvider(event.AggregateID, provider.IDPConfigID, event.Sequence)
|
||||||
|
case model.IDPConfigChanged, org_es_model.IDPConfigChanged:
|
||||||
|
config := new(iam_model.IDPConfig)
|
||||||
|
config.AppendEvent(event)
|
||||||
|
providers, err := m.view.IDPProvidersByIDPConfigID(config.IDPConfigID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if provider.IDPProviderType == int32(iam_model.IDPProviderTypeSystem) {
|
||||||
|
config, err = m.iamEvents.GetIDPConfig(context.Background(), provider.AggregateID, config.IDPConfigID)
|
||||||
|
} else {
|
||||||
|
config, err = m.orgEvents.GetIDPConfig(context.Background(), provider.AggregateID, provider.IDPConfigID)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, provider := range providers {
|
||||||
|
m.fillConfigData(provider, config)
|
||||||
|
}
|
||||||
|
return m.view.PutIDPProviders(event.Sequence, providers...)
|
||||||
|
case org_es_model.LoginPolicyRemoved:
|
||||||
|
return m.view.DeleteIDPProvidersByAggregateID(event.AggregateID, event.Sequence)
|
||||||
|
default:
|
||||||
|
return m.view.ProcessedIDPProviderSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return m.view.PutIDPProvider(provider, provider.Sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *IDPProvider) fillData(provider *iam_view_model.IDPProviderView) (err error) {
|
||||||
|
var config *iam_model.IDPConfig
|
||||||
|
if provider.IDPProviderType == int32(iam_model.IDPProviderTypeSystem) {
|
||||||
|
config, err = m.iamEvents.GetIDPConfig(context.Background(), m.systemDefaults.IamID, provider.IDPConfigID)
|
||||||
|
} else {
|
||||||
|
config, err = m.orgEvents.GetIDPConfig(context.Background(), provider.AggregateID, provider.IDPConfigID)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.fillConfigData(provider, config)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *IDPProvider) fillConfigData(provider *iam_view_model.IDPProviderView, config *iam_model.IDPConfig) {
|
||||||
|
provider.Name = config.Name
|
||||||
|
provider.IDPConfigType = int32(config.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *IDPProvider) OnError(event *models.Event, err error) error {
|
||||||
|
logging.LogWithFields("SPOOL-Fjd89", "id", event.AggregateID).WithError(err).Warn("something went wrong in idp provider handler")
|
||||||
|
return spooler.HandleError(event, err, m.view.GetLatestIDPProviderFailedEvent, m.view.ProcessedIDPProviderFailedEvent, m.view.ProcessedIDPProviderSequence, m.errorCountUntilSkip)
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/logging"
|
||||||
|
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||||
|
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||||
|
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LoginPolicy struct {
|
||||||
|
handler
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
loginPolicyTable = "auth.login_policies"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *LoginPolicy) ViewModel() string {
|
||||||
|
return loginPolicyTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *LoginPolicy) EventQuery() (*models.SearchQuery, error) {
|
||||||
|
sequence, err := m.view.GetLatestLoginPolicySequence()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return es_models.NewSearchQuery().
|
||||||
|
AggregateTypeFilter(model.OrgAggregate, iam_es_model.IAMAggregate).
|
||||||
|
LatestSequenceFilter(sequence.CurrentSequence), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *LoginPolicy) Reduce(event *models.Event) (err error) {
|
||||||
|
switch event.AggregateType {
|
||||||
|
case model.OrgAggregate, iam_es_model.IAMAggregate:
|
||||||
|
err = m.processLoginPolicy(event)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *LoginPolicy) processLoginPolicy(event *models.Event) (err error) {
|
||||||
|
policy := new(iam_model.LoginPolicyView)
|
||||||
|
switch event.Type {
|
||||||
|
case iam_es_model.LoginPolicyAdded, model.LoginPolicyAdded:
|
||||||
|
err = policy.AppendEvent(event)
|
||||||
|
case iam_es_model.LoginPolicyChanged, model.LoginPolicyChanged:
|
||||||
|
policy, err = m.view.LoginPolicyByAggregateID(event.AggregateID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = policy.AppendEvent(event)
|
||||||
|
default:
|
||||||
|
return m.view.ProcessedLoginPolicySequence(event.Sequence)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return m.view.PutLoginPolicy(policy, policy.Sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *LoginPolicy) OnError(event *models.Event, err error) error {
|
||||||
|
logging.LogWithFields("SPOOL-5id9s", "id", event.AggregateID).WithError(err).Warn("something went wrong in login policy handler")
|
||||||
|
return spooler.HandleError(event, err, m.view.GetLatestLoginPolicyFailedEvent, m.view.ProcessedLoginPolicyFailedEvent, m.view.ProcessedLoginPolicySequence, m.errorCountUntilSkip)
|
||||||
|
}
|
@ -0,0 +1,126 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/caos/zitadel/internal/iam/repository/eventsourcing"
|
||||||
|
org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||||
|
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||||
|
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||||
|
usr_view_model "github.com/caos/zitadel/internal/user/repository/view/model"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||||
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
|
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExternalIDP struct {
|
||||||
|
handler
|
||||||
|
systemDefaults systemdefaults.SystemDefaults
|
||||||
|
iamEvents *eventsourcing.IAMEventstore
|
||||||
|
orgEvents *org_es.OrgEventstore
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
externalIDPTable = "auth.user_external_idps"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *ExternalIDP) ViewModel() string {
|
||||||
|
return externalIDPTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) EventQuery() (*models.SearchQuery, error) {
|
||||||
|
sequence, err := m.view.GetLatestExternalIDPSequence()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return es_models.NewSearchQuery().
|
||||||
|
AggregateTypeFilter(model.UserAggregate, iam_es_model.IAMAggregate, org_es_model.OrgAggregate).
|
||||||
|
LatestSequenceFilter(sequence.CurrentSequence), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) Reduce(event *models.Event) (err error) {
|
||||||
|
switch event.AggregateType {
|
||||||
|
case model.UserAggregate:
|
||||||
|
err = m.processUser(event)
|
||||||
|
case iam_es_model.IAMAggregate, org_es_model.OrgAggregate:
|
||||||
|
err = m.processIdpConfig(event)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) processUser(event *models.Event) (err error) {
|
||||||
|
externalIDP := new(usr_view_model.ExternalIDPView)
|
||||||
|
switch event.Type {
|
||||||
|
case model.HumanExternalIDPAdded:
|
||||||
|
err = externalIDP.AppendEvent(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = m.fillData(externalIDP)
|
||||||
|
case model.HumanExternalIDPRemoved, model.HumanExternalIDPCascadeRemoved:
|
||||||
|
err = externalIDP.SetData(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return m.view.DeleteExternalIDP(externalIDP.ExternalUserID, externalIDP.IDPConfigID, event.Sequence)
|
||||||
|
default:
|
||||||
|
return m.view.ProcessedExternalIDPSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return m.view.PutExternalIDP(externalIDP, externalIDP.Sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) processIdpConfig(event *models.Event) (err error) {
|
||||||
|
switch event.Type {
|
||||||
|
case iam_es_model.IDPConfigChanged, org_es_model.IDPConfigChanged:
|
||||||
|
config := new(iam_model.IDPConfig)
|
||||||
|
config.AppendEvent(event)
|
||||||
|
exterinalIDPs, err := m.view.ExternalIDPsByIDPConfigID(config.IDPConfigID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if event.AggregateType == iam_es_model.IAMAggregate {
|
||||||
|
config, err = m.iamEvents.GetIDPConfig(context.Background(), config.AggregateID, config.IDPConfigID)
|
||||||
|
} else {
|
||||||
|
config, err = m.orgEvents.GetIDPConfig(context.Background(), config.AggregateID, config.IDPConfigID)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, provider := range exterinalIDPs {
|
||||||
|
m.fillConfigData(provider, config)
|
||||||
|
}
|
||||||
|
return m.view.PutExternalIDPs(event.Sequence, exterinalIDPs...)
|
||||||
|
default:
|
||||||
|
return m.view.ProcessedExternalIDPSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) fillData(externalIDP *usr_view_model.ExternalIDPView) error {
|
||||||
|
config, err := m.orgEvents.GetIDPConfig(context.Background(), externalIDP.ResourceOwner, externalIDP.IDPConfigID)
|
||||||
|
if caos_errs.IsNotFound(err) {
|
||||||
|
config, err = m.iamEvents.GetIDPConfig(context.Background(), m.systemDefaults.IamID, externalIDP.IDPConfigID)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.fillConfigData(externalIDP, config)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) fillConfigData(externalIDP *usr_view_model.ExternalIDPView, config *iam_model.IDPConfig) {
|
||||||
|
externalIDP.IDPName = config.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) OnError(event *models.Event, err error) error {
|
||||||
|
logging.LogWithFields("SPOOL-4Rsu8", "id", event.AggregateID).WithError(err).Warn("something went wrong in idp provider handler")
|
||||||
|
return spooler.HandleError(event, err, m.view.GetLatestExternalIDPFailedEvent, m.view.ProcessedExternalIDPFailedEvent, m.view.ProcessedExternalIDPSequence, m.errorCountUntilSkip)
|
||||||
|
}
|
@ -128,6 +128,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, au
|
|||||||
return &EsRepository{
|
return &EsRepository{
|
||||||
spool,
|
spool,
|
||||||
eventstore.UserRepo{
|
eventstore.UserRepo{
|
||||||
|
SearchLimit: conf.SearchLimit,
|
||||||
Eventstore: es,
|
Eventstore: es,
|
||||||
UserEvents: user,
|
UserEvents: user,
|
||||||
OrgEvents: org,
|
OrgEvents: org,
|
||||||
@ -136,17 +137,22 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, au
|
|||||||
},
|
},
|
||||||
eventstore.AuthRequestRepo{
|
eventstore.AuthRequestRepo{
|
||||||
UserEvents: user,
|
UserEvents: user,
|
||||||
|
OrgEvents: org,
|
||||||
|
PolicyEvents: policy,
|
||||||
AuthRequests: authReq,
|
AuthRequests: authReq,
|
||||||
View: view,
|
View: view,
|
||||||
UserSessionViewProvider: view,
|
UserSessionViewProvider: view,
|
||||||
UserViewProvider: view,
|
UserViewProvider: view,
|
||||||
UserEventProvider: user,
|
UserEventProvider: user,
|
||||||
OrgViewProvider: view,
|
OrgViewProvider: view,
|
||||||
|
IDPProviderViewProvider: view,
|
||||||
|
LoginPolicyViewProvider: view,
|
||||||
IdGenerator: idGenerator,
|
IdGenerator: idGenerator,
|
||||||
PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration,
|
PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration,
|
||||||
MfaInitSkippedLifeTime: systemDefaults.VerificationLifetimes.MfaInitSkip.Duration,
|
MfaInitSkippedLifeTime: systemDefaults.VerificationLifetimes.MfaInitSkip.Duration,
|
||||||
MfaSoftwareCheckLifeTime: systemDefaults.VerificationLifetimes.MfaSoftwareCheck.Duration,
|
MfaSoftwareCheckLifeTime: systemDefaults.VerificationLifetimes.MfaSoftwareCheck.Duration,
|
||||||
MfaHardwareCheckLifeTime: systemDefaults.VerificationLifetimes.MfaHardwareCheck.Duration,
|
MfaHardwareCheckLifeTime: systemDefaults.VerificationLifetimes.MfaHardwareCheck.Duration,
|
||||||
|
IAMID: systemDefaults.IamID,
|
||||||
},
|
},
|
||||||
eventstore.TokenRepo{View: view},
|
eventstore.TokenRepo{View: view},
|
||||||
eventstore.KeyRepository{
|
eventstore.KeyRepository{
|
||||||
|
73
internal/auth/repository/eventsourcing/view/external_idps.go
Normal file
73
internal/auth/repository/eventsourcing/view/external_idps.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||||
|
"github.com/caos/zitadel/internal/user/repository/view"
|
||||||
|
"github.com/caos/zitadel/internal/user/repository/view/model"
|
||||||
|
global_view "github.com/caos/zitadel/internal/view/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
externalIDPTable = "auth.user_external_idps"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *View) ExternalIDPByExternalUserIDAndIDPConfigID(externalUserID, idpConfigID string) (*model.ExternalIDPView, error) {
|
||||||
|
return view.ExternalIDPByExternalUserIDAndIDPConfigID(v.Db, externalIDPTable, externalUserID, idpConfigID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ExternalIDPByExternalUserIDAndIDPConfigIDAndResourceOwner(externalUserID, idpConfigID, resourceOwner string) (*model.ExternalIDPView, error) {
|
||||||
|
return view.ExternalIDPByExternalUserIDAndIDPConfigIDAndResourceOwner(v.Db, externalIDPTable, externalUserID, idpConfigID, resourceOwner)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ExternalIDPsByIDPConfigID(idpConfigID string) ([]*model.ExternalIDPView, error) {
|
||||||
|
return view.ExternalIDPsByIDPConfigID(v.Db, externalIDPTable, idpConfigID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ExternalIDPsByUserID(userID string) ([]*model.ExternalIDPView, error) {
|
||||||
|
return view.ExternalIDPsByUserID(v.Db, externalIDPTable, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) SearchExternalIDPs(request *usr_model.ExternalIDPSearchRequest) ([]*model.ExternalIDPView, uint64, error) {
|
||||||
|
return view.SearchExternalIDPs(v.Db, externalIDPTable, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) PutExternalIDP(externalIDP *model.ExternalIDPView, sequence uint64) error {
|
||||||
|
err := view.PutExternalIDP(v.Db, externalIDPTable, externalIDP)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedExternalIDPSequence(sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) PutExternalIDPs(sequence uint64, externalIDPs ...*model.ExternalIDPView) error {
|
||||||
|
err := view.PutExternalIDPs(v.Db, externalIDPTable, externalIDPs...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedExternalIDPSequence(sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) DeleteExternalIDP(externalUserID, idpConfigID string, eventSequence uint64) error {
|
||||||
|
err := view.DeleteExternalIDP(v.Db, externalIDPTable, externalUserID, idpConfigID)
|
||||||
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedExternalIDPSequence(eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestExternalIDPSequence() (*global_view.CurrentSequence, error) {
|
||||||
|
return v.latestSequence(externalIDPTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedExternalIDPSequence(eventSequence uint64) error {
|
||||||
|
return v.saveCurrentSequence(externalIDPTable, eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestExternalIDPFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||||
|
return v.latestFailedEvent(externalIDPTable, sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedExternalIDPFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||||
|
return v.saveFailedEvent(failedEvent)
|
||||||
|
}
|
57
internal/auth/repository/eventsourcing/view/idp_configs.go
Normal file
57
internal/auth/repository/eventsourcing/view/idp_configs.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
|
"github.com/caos/zitadel/internal/iam/repository/view"
|
||||||
|
iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||||
|
global_view "github.com/caos/zitadel/internal/view/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
idpConfigTable = "auth.idp_configs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *View) IDPConfigByID(idpID string) (*iam_es_model.IDPConfigView, error) {
|
||||||
|
return view.IDPByID(v.Db, idpConfigTable, idpID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetIDPConfigsByAggregateID(aggregateID string) ([]*iam_es_model.IDPConfigView, error) {
|
||||||
|
return view.GetIDPConfigsByAggregateID(v.Db, idpConfigTable, aggregateID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) SearchIDPConfigs(request *iam_model.IDPConfigSearchRequest) ([]*iam_es_model.IDPConfigView, uint64, error) {
|
||||||
|
return view.SearchIDPs(v.Db, idpConfigTable, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) PutIDPConfig(idp *iam_es_model.IDPConfigView, sequence uint64) error {
|
||||||
|
err := view.PutIDP(v.Db, idpConfigTable, idp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedIDPConfigSequence(sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) DeleteIDPConfig(idpID string, eventSequence uint64) error {
|
||||||
|
err := view.DeleteIDP(v.Db, idpConfigTable, idpID)
|
||||||
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedIDPConfigSequence(eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestIDPConfigSequence() (*global_view.CurrentSequence, error) {
|
||||||
|
return v.latestSequence(idpConfigTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedIDPConfigSequence(eventSequence uint64) error {
|
||||||
|
return v.saveCurrentSequence(idpConfigTable, eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestIDPConfigFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||||
|
return v.latestFailedEvent(idpConfigTable, sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedIDPConfigFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||||
|
return v.saveFailedEvent(failedEvent)
|
||||||
|
}
|
77
internal/auth/repository/eventsourcing/view/idp_providers.go
Normal file
77
internal/auth/repository/eventsourcing/view/idp_providers.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
|
"github.com/caos/zitadel/internal/iam/repository/view"
|
||||||
|
"github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||||
|
global_view "github.com/caos/zitadel/internal/view/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
idpProviderTable = "auth.idp_providers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *View) IDPProviderByAggregateAndIDPConfigID(aggregateID, idpConfigID string) (*model.IDPProviderView, error) {
|
||||||
|
return view.GetIDPProviderByAggregateIDAndConfigID(v.Db, idpProviderTable, aggregateID, idpConfigID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) IDPProvidersByIDPConfigID(idpConfigID string) ([]*model.IDPProviderView, error) {
|
||||||
|
return view.IDPProvidersByIdpConfigID(v.Db, idpProviderTable, idpConfigID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) IDPProvidersByAggregateID(aggregateID string) ([]*model.IDPProviderView, error) {
|
||||||
|
return view.IDPProvidersByAggregateID(v.Db, idpProviderTable, aggregateID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) SearchIDPProviders(request *iam_model.IDPProviderSearchRequest) ([]*model.IDPProviderView, uint64, error) {
|
||||||
|
return view.SearchIDPProviders(v.Db, idpProviderTable, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) PutIDPProvider(provider *model.IDPProviderView, sequence uint64) error {
|
||||||
|
err := view.PutIDPProvider(v.Db, idpProviderTable, provider)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedIDPProviderSequence(sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) PutIDPProviders(sequence uint64, providers ...*model.IDPProviderView) error {
|
||||||
|
err := view.PutIDPProviders(v.Db, idpProviderTable, providers...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedIDPProviderSequence(sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) DeleteIDPProvider(aggregateID, idpConfigID string, eventSequence uint64) error {
|
||||||
|
err := view.DeleteIDPProvider(v.Db, idpProviderTable, aggregateID, idpConfigID)
|
||||||
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedIDPProviderSequence(eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) DeleteIDPProvidersByAggregateID(aggregateID string, eventSequence uint64) error {
|
||||||
|
err := view.DeleteIDPProvidersByAggregateID(v.Db, idpProviderTable, aggregateID)
|
||||||
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedIDPProviderSequence(eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestIDPProviderSequence() (*global_view.CurrentSequence, error) {
|
||||||
|
return v.latestSequence(idpProviderTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedIDPProviderSequence(eventSequence uint64) error {
|
||||||
|
return v.saveCurrentSequence(idpProviderTable, eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestIDPProviderFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||||
|
return v.latestFailedEvent(idpProviderTable, sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedIDPProviderFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||||
|
return v.saveFailedEvent(failedEvent)
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/caos/zitadel/internal/iam/repository/view"
|
||||||
|
"github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||||
|
global_view "github.com/caos/zitadel/internal/view/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
loginPolicyTable = "auth.login_policies"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *View) LoginPolicyByAggregateID(aggregateID string) (*model.LoginPolicyView, error) {
|
||||||
|
return view.GetLoginPolicyByAggregateID(v.Db, loginPolicyTable, aggregateID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) PutLoginPolicy(policy *model.LoginPolicyView, sequence uint64) error {
|
||||||
|
err := view.PutLoginPolicy(v.Db, loginPolicyTable, policy)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedLoginPolicySequence(sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) DeleteLoginPolicy(aggregateID string, eventSequence uint64) error {
|
||||||
|
err := view.DeleteLoginPolicy(v.Db, loginPolicyTable, aggregateID)
|
||||||
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedLoginPolicySequence(eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestLoginPolicySequence() (*global_view.CurrentSequence, error) {
|
||||||
|
return v.latestSequence(loginPolicyTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedLoginPolicySequence(eventSequence uint64) error {
|
||||||
|
return v.saveCurrentSequence(loginPolicyTable, eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestLoginPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||||
|
return v.latestFailedEvent(loginPolicyTable, sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedLoginPolicyFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||||
|
return v.saveFailedEvent(failedEvent)
|
||||||
|
}
|
@ -23,6 +23,10 @@ func (v *View) UserByLoginName(loginName string) (*model.UserView, error) {
|
|||||||
return view.UserByLoginName(v.Db, userTable, loginName)
|
return view.UserByLoginName(v.Db, userTable, loginName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *View) UserByLoginNameAndResourceOwner(loginName, resourceOwner string) (*model.UserView, error) {
|
||||||
|
return view.UserByLoginNameAndResourceOwner(v.Db, userTable, loginName, resourceOwner)
|
||||||
|
}
|
||||||
|
|
||||||
func (v *View) UsersByOrgID(orgID string) ([]*model.UserView, error) {
|
func (v *View) UsersByOrgID(orgID string) ([]*model.UserView, error) {
|
||||||
return view.UsersByOrgID(v.Db, userTable, orgID)
|
return view.UsersByOrgID(v.Db, userTable, orgID)
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,13 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
auth_model "github.com/caos/zitadel/internal/auth/model"
|
auth_model "github.com/caos/zitadel/internal/auth/model"
|
||||||
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
org_model "github.com/caos/zitadel/internal/org/model"
|
org_model "github.com/caos/zitadel/internal/org/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OrgRepository interface {
|
type OrgRepository interface {
|
||||||
RegisterOrg(context.Context, *auth_model.RegisterOrg) (*auth_model.RegisterOrg, error)
|
RegisterOrg(context.Context, *auth_model.RegisterOrg) (*auth_model.RegisterOrg, error)
|
||||||
GetOrgIamPolicy(ctx context.Context, orgID string) (*org_model.OrgIAMPolicy, error)
|
GetOrgIamPolicy(ctx context.Context, orgID string) (*org_model.OrgIAMPolicy, error)
|
||||||
|
GetDefaultOrgIamPolicy(ctx context.Context) *org_model.OrgIAMPolicy
|
||||||
|
GetIDPConfigByID(ctx context.Context, idpConfigID string) (*iam_model.IDPConfigView, error)
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
type UserRepository interface {
|
type UserRepository interface {
|
||||||
Register(ctx context.Context, user *model.User, member *org_model.OrgMember, resourceOwner string) (*model.User, error)
|
Register(ctx context.Context, user *model.User, member *org_model.OrgMember, resourceOwner string) (*model.User, error)
|
||||||
|
RegisterExternalUser(ctx context.Context, user *model.User, externalIDP *model.ExternalIDP, member *org_model.OrgMember, resourceOwner string) (*model.User, error)
|
||||||
|
|
||||||
myUserRepo
|
myUserRepo
|
||||||
SkipMfaInit(ctx context.Context, userID string) error
|
SkipMfaInit(ctx context.Context, userID string) error
|
||||||
@ -58,6 +59,10 @@ type myUserRepo interface {
|
|||||||
|
|
||||||
ChangeMyPassword(ctx context.Context, old, new string) error
|
ChangeMyPassword(ctx context.Context, old, new string) error
|
||||||
|
|
||||||
|
SearchMyExternalIDPs(ctx context.Context, request *model.ExternalIDPSearchRequest) (*model.ExternalIDPSearchResponse, error)
|
||||||
|
AddMyExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) (*model.ExternalIDP, error)
|
||||||
|
RemoveMyExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) error
|
||||||
|
|
||||||
MyUserMfas(ctx context.Context) ([]*model.MultiFactor, error)
|
MyUserMfas(ctx context.Context) ([]*model.MultiFactor, error)
|
||||||
AddMyMfaOTP(ctx context.Context) (*model.OTP, error)
|
AddMyMfaOTP(ctx context.Context) (*model.OTP, error)
|
||||||
VerifyMyMfaOTPSetup(ctx context.Context, code string) error
|
VerifyMyMfaOTPSetup(ctx context.Context, code string) error
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/iam/model"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/errors"
|
"github.com/caos/zitadel/internal/errors"
|
||||||
@ -22,17 +25,36 @@ type AuthRequest struct {
|
|||||||
MaxAuthAge uint32
|
MaxAuthAge uint32
|
||||||
Request Request
|
Request Request
|
||||||
|
|
||||||
levelOfAssurance LevelOfAssurance
|
levelOfAssurance LevelOfAssurance
|
||||||
UserID string
|
UserID string
|
||||||
LoginName string
|
LoginName string
|
||||||
DisplayName string
|
DisplayName string
|
||||||
UserOrgID string
|
UserOrgID string
|
||||||
PossibleSteps []NextStep
|
SelectedIDPConfigID string
|
||||||
PasswordVerified bool
|
LinkingUsers []*ExternalUser
|
||||||
MfasVerified []MfaType
|
PossibleSteps []NextStep
|
||||||
Audience []string
|
PasswordVerified bool
|
||||||
AuthTime time.Time
|
MfasVerified []MfaType
|
||||||
Code string
|
Audience []string
|
||||||
|
AuthTime time.Time
|
||||||
|
Code string
|
||||||
|
LoginPolicy *model.LoginPolicyView
|
||||||
|
AllowedExternalIDPs []*model.IDPProviderView
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExternalUser struct {
|
||||||
|
IDPConfigID string
|
||||||
|
ExternalUserID string
|
||||||
|
DisplayName string
|
||||||
|
PreferredUsername string
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
NickName string
|
||||||
|
Email string
|
||||||
|
IsEmailVerified bool
|
||||||
|
PreferredLanguage language.Tag
|
||||||
|
Phone string
|
||||||
|
IsPhoneVerified bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Prompt int32
|
type Prompt int32
|
||||||
@ -103,3 +125,15 @@ func (a *AuthRequest) SetUserInfo(userID, loginName, displayName, userOrgID stri
|
|||||||
a.DisplayName = displayName
|
a.DisplayName = displayName
|
||||||
a.UserOrgID = userOrgID
|
a.UserOrgID = userOrgID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *AuthRequest) GetScopeOrgID() string {
|
||||||
|
switch request := a.Request.(type) {
|
||||||
|
case *AuthRequestOIDC:
|
||||||
|
for _, scope := range request.Scopes {
|
||||||
|
if strings.HasPrefix(scope, OrgIDScope) {
|
||||||
|
strings.TrimPrefix(scope, OrgIDScope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
@ -19,6 +19,8 @@ const (
|
|||||||
NextStepMfaVerify
|
NextStepMfaVerify
|
||||||
NextStepRedirectToCallback
|
NextStepRedirectToCallback
|
||||||
NextStepChangeUsername
|
NextStepChangeUsername
|
||||||
|
NextStepLinkUsers
|
||||||
|
NextStepExternalNotFoundOption
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserSessionState int32
|
type UserSessionState int32
|
||||||
@ -53,6 +55,12 @@ type InitUserStep struct {
|
|||||||
PasswordSet bool
|
PasswordSet bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExternalNotFoundOptionStep struct{}
|
||||||
|
|
||||||
|
func (s *ExternalNotFoundOptionStep) Type() NextStepType {
|
||||||
|
return NextStepExternalNotFoundOption
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InitUserStep) Type() NextStepType {
|
func (s *InitUserStep) Type() NextStepType {
|
||||||
return NextStepInitUser
|
return NextStepInitUser
|
||||||
}
|
}
|
||||||
@ -104,6 +112,12 @@ func (s *MfaVerificationStep) Type() NextStepType {
|
|||||||
return NextStepMfaVerify
|
return NextStepMfaVerify
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LinkUsersStep struct{}
|
||||||
|
|
||||||
|
func (s *LinkUsersStep) Type() NextStepType {
|
||||||
|
return NextStepLinkUsers
|
||||||
|
}
|
||||||
|
|
||||||
type RedirectToCallbackStep struct{}
|
type RedirectToCallbackStep struct{}
|
||||||
|
|
||||||
func (s *RedirectToCallbackStep) Type() NextStepType {
|
func (s *RedirectToCallbackStep) Type() NextStepType {
|
||||||
|
@ -18,6 +18,10 @@ const (
|
|||||||
AuthRequestTypeSAML
|
AuthRequestTypeSAML
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
OrgIDScope = "urn:zitadel:organisation:id:"
|
||||||
|
)
|
||||||
|
|
||||||
type AuthRequestOIDC struct {
|
type AuthRequestOIDC struct {
|
||||||
Scopes []string
|
Scopes []string
|
||||||
ResponseType OIDCResponseType
|
ResponseType OIDCResponseType
|
||||||
|
@ -17,12 +17,14 @@ type IDPConfig struct {
|
|||||||
|
|
||||||
type OIDCIDPConfig struct {
|
type OIDCIDPConfig struct {
|
||||||
es_models.ObjectRoot
|
es_models.ObjectRoot
|
||||||
IDPConfigID string
|
IDPConfigID string
|
||||||
ClientID string
|
ClientID string
|
||||||
ClientSecret *crypto.CryptoValue
|
ClientSecret *crypto.CryptoValue
|
||||||
ClientSecretString string
|
ClientSecretString string
|
||||||
Issuer string
|
Issuer string
|
||||||
Scopes []string
|
Scopes []string
|
||||||
|
IDPDisplayNameMapping OIDCMappingField
|
||||||
|
UsernameMapping OIDCMappingField
|
||||||
}
|
}
|
||||||
|
|
||||||
type IdpConfigType int32
|
type IdpConfigType int32
|
||||||
@ -40,6 +42,14 @@ const (
|
|||||||
IDPConfigStateRemoved
|
IDPConfigStateRemoved
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type OIDCMappingField int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
OIDCMappingFieldUnspecified OIDCMappingField = iota
|
||||||
|
OIDCMappingFieldPreferredLoginName
|
||||||
|
OIDCMappingFieldEmail
|
||||||
|
)
|
||||||
|
|
||||||
func NewIDPConfig(iamID, idpID string) *IDPConfig {
|
func NewIDPConfig(iamID, idpID string) *IDPConfig {
|
||||||
return &IDPConfig{ObjectRoot: es_models.ObjectRoot{AggregateID: iamID}, IDPConfigID: idpID}
|
return &IDPConfig{ObjectRoot: es_models.ObjectRoot{AggregateID: iamID}, IDPConfigID: idpID}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,13 @@ type IDPConfigView struct {
|
|||||||
Sequence uint64
|
Sequence uint64
|
||||||
IDPProviderType IDPProviderType
|
IDPProviderType IDPProviderType
|
||||||
|
|
||||||
IsOIDC bool
|
IsOIDC bool
|
||||||
OIDCClientID string
|
OIDCClientID string
|
||||||
OIDCClientSecret *crypto.CryptoValue
|
OIDCClientSecret *crypto.CryptoValue
|
||||||
OIDCIssuer string
|
OIDCIssuer string
|
||||||
OIDCScopes []string
|
OIDCScopes []string
|
||||||
|
OIDCIDPDisplayNameMapping OIDCMappingField
|
||||||
|
OIDCUsernameMapping OIDCMappingField
|
||||||
}
|
}
|
||||||
|
|
||||||
type IDPConfigSearchRequest struct {
|
type IDPConfigSearchRequest struct {
|
||||||
|
@ -380,7 +380,10 @@ func (es *IAMEventstore) ChangeIDPOIDCConfig(ctx context.Context, config *iam_mo
|
|||||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Fms8w", "Errors.IAM.IdpIsNotOIDC")
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Fms8w", "Errors.IAM.IdpIsNotOIDC")
|
||||||
}
|
}
|
||||||
if config.ClientSecretString != "" {
|
if config.ClientSecretString != "" {
|
||||||
err = idp.OIDCConfig.CryptSecret(es.secretCrypto)
|
err = config.CryptSecret(es.secretCrypto)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
config.ClientSecret = nil
|
config.ClientSecret = nil
|
||||||
}
|
}
|
||||||
@ -467,20 +470,31 @@ func (es *IAMEventstore) AddIDPProviderToLoginPolicy(ctx context.Context, provid
|
|||||||
return nil, caos_errs.ThrowInternal(nil, "EVENT-Slf9s", "Errors.Internal")
|
return nil, caos_errs.ThrowInternal(nil, "EVENT-Slf9s", "Errors.Internal")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *IAMEventstore) RemoveIDPProviderFromLoginPolicy(ctx context.Context, provider *iam_model.IDPProvider) error {
|
func (es *IAMEventstore) PrepareRemoveIDPProviderFromLoginPolicy(ctx context.Context, provider *iam_model.IDPProvider) (*model.IAM, *models.Aggregate, error) {
|
||||||
if provider == nil || !provider.IsValid() {
|
if provider == nil || !provider.IsValid() {
|
||||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-Esi8c", "Errors.IdpProviderInvalid")
|
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Esi8c", "Errors.IdpProviderInvalid")
|
||||||
}
|
}
|
||||||
iam, err := es.IAMByID(ctx, provider.AggregateID)
|
iam, err := es.IAMByID(ctx, provider.AggregateID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if _, m := iam.DefaultLoginPolicy.GetIdpProvider(provider.IdpConfigID); m == nil {
|
if _, m := iam.DefaultLoginPolicy.GetIdpProvider(provider.IdpConfigID); m == nil {
|
||||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-29skr", "Errors.IAM.LoginPolicy.IdpProviderNotExisting")
|
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-29skr", "Errors.IAM.LoginPolicy.IdpProviderNotExisting")
|
||||||
}
|
}
|
||||||
repoIam := model.IAMFromModel(iam)
|
repoIam := model.IAMFromModel(iam)
|
||||||
addAggregate := LoginPolicyIDPProviderRemovedAggregate(es.Eventstore.AggregateCreator(), repoIam, &model.IDPProviderID{provider.IdpConfigID})
|
removeAgg, err := LoginPolicyIDPProviderRemovedAggregate(ctx, es.Eventstore.AggregateCreator(), repoIam, &model.IDPProviderID{provider.IdpConfigID})
|
||||||
err = es_sdk.Push(ctx, es.PushAggregates, repoIam.AppendEvents, addAggregate)
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return repoIam, removeAgg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *IAMEventstore) RemoveIDPProviderFromLoginPolicy(ctx context.Context, provider *iam_model.IDPProvider) error {
|
||||||
|
repoIam, removeAgg, err := es.PrepareRemoveIDPProviderFromLoginPolicy(ctx, provider)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = es_sdk.PushAggregates(ctx, es.PushAggregates, repoIam.AppendEvents, removeAgg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -285,17 +285,15 @@ func LoginPolicyIDPProviderAddedAggregate(aggCreator *es_models.AggregateCreator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoginPolicyIDPProviderRemovedAggregate(aggCreator *es_models.AggregateCreator, existing *model.IAM, provider *model.IDPProviderID) func(ctx context.Context) (*es_models.Aggregate, error) {
|
func LoginPolicyIDPProviderRemovedAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *model.IAM, provider *model.IDPProviderID) (*es_models.Aggregate, error) {
|
||||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
if provider == nil || existing == nil {
|
||||||
if provider == nil || existing == nil {
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-Sml9d", "Errors.Internal")
|
||||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-Sml9d", "Errors.Internal")
|
|
||||||
}
|
|
||||||
agg, err := IAMAggregate(ctx, aggCreator, existing)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return agg.AppendEvent(model.LoginPolicyIDPProviderRemoved, provider)
|
|
||||||
}
|
}
|
||||||
|
agg, err := IAMAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(model.LoginPolicyIDPProviderRemoved, provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkExistingLoginPolicyValidation() func(...*es_models.Event) error {
|
func checkExistingLoginPolicyValidation() func(...*es_models.Event) error {
|
||||||
|
@ -1447,7 +1447,7 @@ func TestLoginPolicyIdpProviderRemovedAggregate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
agg, err := LoginPolicyIDPProviderRemovedAggregate(tt.args.aggCreator, tt.args.existingIAM, tt.args.newProviderID)(tt.args.ctx)
|
agg, err := LoginPolicyIDPProviderRemovedAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.existingIAM, tt.args.newProviderID)
|
||||||
|
|
||||||
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
|
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
|
||||||
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
||||||
|
@ -10,11 +10,12 @@ import (
|
|||||||
|
|
||||||
type IDPConfig struct {
|
type IDPConfig struct {
|
||||||
es_models.ObjectRoot
|
es_models.ObjectRoot
|
||||||
IDPConfigID string `json:"idpConfigId"`
|
IDPConfigID string `json:"idpConfigId"`
|
||||||
State int32 `json:"-"`
|
State int32 `json:"-"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Type int32 `json:"idpType,omitempty"`
|
Type int32 `json:"idpType,omitempty"`
|
||||||
LogoSrc []byte `json:"logoSrc,omitempty"`
|
LogoSrc []byte `json:"logoSrc,omitempty"`
|
||||||
|
|
||||||
OIDCIDPConfig *OIDCIDPConfig `json:"-"`
|
OIDCIDPConfig *OIDCIDPConfig `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,11 +12,13 @@ import (
|
|||||||
|
|
||||||
type OIDCIDPConfig struct {
|
type OIDCIDPConfig struct {
|
||||||
es_models.ObjectRoot
|
es_models.ObjectRoot
|
||||||
IDPConfigID string `json:"idpConfigId"`
|
IDPConfigID string `json:"idpConfigId"`
|
||||||
ClientID string `json:"clientId"`
|
ClientID string `json:"clientId"`
|
||||||
ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"`
|
ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"`
|
||||||
Issuer string `json:"issuer,omitempty"`
|
Issuer string `json:"issuer,omitempty"`
|
||||||
Scopes pq.StringArray `json:"scopes,omitempty"`
|
Scopes pq.StringArray `json:"scopes,omitempty"`
|
||||||
|
IDPDisplayNameMapping int32 `json:"idpDisplayNameMapping,omitempty"`
|
||||||
|
UsernameMapping int32 `json:"usernameMapping,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *OIDCIDPConfig) Changes(changed *OIDCIDPConfig) map[string]interface{} {
|
func (c *OIDCIDPConfig) Changes(changed *OIDCIDPConfig) map[string]interface{} {
|
||||||
@ -25,7 +27,7 @@ func (c *OIDCIDPConfig) Changes(changed *OIDCIDPConfig) map[string]interface{} {
|
|||||||
if c.ClientID != changed.ClientID {
|
if c.ClientID != changed.ClientID {
|
||||||
changes["clientId"] = changed.ClientID
|
changes["clientId"] = changed.ClientID
|
||||||
}
|
}
|
||||||
if c.ClientSecret != nil {
|
if c.ClientSecret != nil && c.ClientSecret != changed.ClientSecret {
|
||||||
changes["clientSecret"] = changed.ClientSecret
|
changes["clientSecret"] = changed.ClientSecret
|
||||||
}
|
}
|
||||||
if c.Issuer != changed.Issuer {
|
if c.Issuer != changed.Issuer {
|
||||||
@ -34,28 +36,38 @@ func (c *OIDCIDPConfig) Changes(changed *OIDCIDPConfig) map[string]interface{} {
|
|||||||
if !reflect.DeepEqual(c.Scopes, changed.Scopes) {
|
if !reflect.DeepEqual(c.Scopes, changed.Scopes) {
|
||||||
changes["scopes"] = changed.Scopes
|
changes["scopes"] = changed.Scopes
|
||||||
}
|
}
|
||||||
|
if c.IDPDisplayNameMapping != changed.IDPDisplayNameMapping {
|
||||||
|
changes["idpDisplayNameMapping"] = changed.IDPDisplayNameMapping
|
||||||
|
}
|
||||||
|
if c.UsernameMapping != changed.UsernameMapping {
|
||||||
|
changes["usernameMapping"] = changed.UsernameMapping
|
||||||
|
}
|
||||||
return changes
|
return changes
|
||||||
}
|
}
|
||||||
|
|
||||||
func OIDCIDPConfigFromModel(config *model.OIDCIDPConfig) *OIDCIDPConfig {
|
func OIDCIDPConfigFromModel(config *model.OIDCIDPConfig) *OIDCIDPConfig {
|
||||||
return &OIDCIDPConfig{
|
return &OIDCIDPConfig{
|
||||||
ObjectRoot: config.ObjectRoot,
|
ObjectRoot: config.ObjectRoot,
|
||||||
IDPConfigID: config.IDPConfigID,
|
IDPConfigID: config.IDPConfigID,
|
||||||
ClientID: config.ClientID,
|
ClientID: config.ClientID,
|
||||||
ClientSecret: config.ClientSecret,
|
ClientSecret: config.ClientSecret,
|
||||||
Issuer: config.Issuer,
|
Issuer: config.Issuer,
|
||||||
Scopes: config.Scopes,
|
Scopes: config.Scopes,
|
||||||
|
IDPDisplayNameMapping: int32(config.IDPDisplayNameMapping),
|
||||||
|
UsernameMapping: int32(config.UsernameMapping),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func OIDCIDPConfigToModel(config *OIDCIDPConfig) *model.OIDCIDPConfig {
|
func OIDCIDPConfigToModel(config *OIDCIDPConfig) *model.OIDCIDPConfig {
|
||||||
return &model.OIDCIDPConfig{
|
return &model.OIDCIDPConfig{
|
||||||
ObjectRoot: config.ObjectRoot,
|
ObjectRoot: config.ObjectRoot,
|
||||||
IDPConfigID: config.IDPConfigID,
|
IDPConfigID: config.IDPConfigID,
|
||||||
ClientID: config.ClientID,
|
ClientID: config.ClientID,
|
||||||
ClientSecret: config.ClientSecret,
|
ClientSecret: config.ClientSecret,
|
||||||
Issuer: config.Issuer,
|
Issuer: config.Issuer,
|
||||||
Scopes: config.Scopes,
|
Scopes: config.Scopes,
|
||||||
|
IDPDisplayNameMapping: model.OIDCMappingField(config.IDPDisplayNameMapping),
|
||||||
|
UsernameMapping: model.OIDCMappingField(config.UsernameMapping),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ func GetIDPProviderByAggregateIDAndConfigID(db *gorm.DB, table, aggregateID, idp
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IDPProvidersByIdpConfigID(db *gorm.DB, table string, idpConfigID string) ([]*model.IDPProviderView, error) {
|
func IDPProvidersByIdpConfigID(db *gorm.DB, table string, idpConfigID string) ([]*model.IDPProviderView, error) {
|
||||||
members := make([]*model.IDPProviderView, 0)
|
providers := make([]*model.IDPProviderView, 0)
|
||||||
queries := []*iam_model.IDPProviderSearchQuery{
|
queries := []*iam_model.IDPProviderSearchQuery{
|
||||||
{
|
{
|
||||||
Key: iam_model.IDPProviderSearchKeyIdpConfigID,
|
Key: iam_model.IDPProviderSearchKeyIdpConfigID,
|
||||||
@ -31,11 +31,28 @@ func IDPProvidersByIdpConfigID(db *gorm.DB, table string, idpConfigID string) ([
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
query := repository.PrepareSearchQuery(table, model.IDPProviderSearchRequest{Queries: queries})
|
query := repository.PrepareSearchQuery(table, model.IDPProviderSearchRequest{Queries: queries})
|
||||||
_, err := query(db, &members)
|
_, err := query(db, &providers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return members, nil
|
return providers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func IDPProvidersByAggregateID(db *gorm.DB, table string, aggregateID string) ([]*model.IDPProviderView, error) {
|
||||||
|
providers := make([]*model.IDPProviderView, 0)
|
||||||
|
queries := []*iam_model.IDPProviderSearchQuery{
|
||||||
|
{
|
||||||
|
Key: iam_model.IDPProviderSearchKeyAggregateID,
|
||||||
|
Value: aggregateID,
|
||||||
|
Method: global_model.SearchMethodEquals,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
query := repository.PrepareSearchQuery(table, model.IDPProviderSearchRequest{Queries: queries})
|
||||||
|
_, err := query(db, &providers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return providers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SearchIDPProviders(db *gorm.DB, table string, req *iam_model.IDPProviderSearchRequest) ([]*model.IDPProviderView, uint64, error) {
|
func SearchIDPProviders(db *gorm.DB, table string, req *iam_model.IDPProviderSearchRequest) ([]*model.IDPProviderView, uint64, error) {
|
||||||
|
@ -20,6 +20,23 @@ func IDPByID(db *gorm.DB, table, idpID string) (*model.IDPConfigView, error) {
|
|||||||
return idp, err
|
return idp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetIDPConfigsByAggregateID(db *gorm.DB, table string, aggregateID string) ([]*model.IDPConfigView, error) {
|
||||||
|
idps := make([]*model.IDPConfigView, 0)
|
||||||
|
queries := []*iam_model.IDPConfigSearchQuery{
|
||||||
|
{
|
||||||
|
Key: iam_model.IDPConfigSearchKeyAggregateID,
|
||||||
|
Value: aggregateID,
|
||||||
|
Method: global_model.SearchMethodEquals,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
query := repository.PrepareSearchQuery(table, model.IDPConfigSearchRequest{Queries: queries})
|
||||||
|
_, err := query(db, &idps)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return idps, nil
|
||||||
|
}
|
||||||
|
|
||||||
func SearchIDPs(db *gorm.DB, table string, req *iam_model.IDPConfigSearchRequest) ([]*model.IDPConfigView, uint64, error) {
|
func SearchIDPs(db *gorm.DB, table string, req *iam_model.IDPConfigSearchRequest) ([]*model.IDPConfigView, uint64, error) {
|
||||||
idps := make([]*model.IDPConfigView, 0)
|
idps := make([]*model.IDPConfigView, 0)
|
||||||
query := repository.PrepareSearchQuery(table, model.IDPConfigSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
|
query := repository.PrepareSearchQuery(table, model.IDPConfigSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
|
||||||
|
@ -32,55 +32,61 @@ type IDPConfigView struct {
|
|||||||
IDPState int32 `json:"-" gorm:"column:idp_state"`
|
IDPState int32 `json:"-" gorm:"column:idp_state"`
|
||||||
IDPProviderType int32 `json:"-" gorm:"column:idp_provider_type"`
|
IDPProviderType int32 `json:"-" gorm:"column:idp_provider_type"`
|
||||||
|
|
||||||
IsOIDC bool `json:"-" gorm:"column:is_oidc"`
|
IsOIDC bool `json:"-" gorm:"column:is_oidc"`
|
||||||
OIDCClientID string `json:"clientId" gorm:"column:oidc_client_id"`
|
OIDCClientID string `json:"clientId" gorm:"column:oidc_client_id"`
|
||||||
OIDCClientSecret *crypto.CryptoValue `json:"clientSecret" gorm:"column:oidc_client_secret"`
|
OIDCClientSecret *crypto.CryptoValue `json:"clientSecret" gorm:"column:oidc_client_secret"`
|
||||||
OIDCIssuer string `json:"issuer" gorm:"column:oidc_issuer"`
|
OIDCIssuer string `json:"issuer" gorm:"column:oidc_issuer"`
|
||||||
OIDCScopes pq.StringArray `json:"scopes" gorm:"column:oidc_scopes"`
|
OIDCScopes pq.StringArray `json:"scopes" gorm:"column:oidc_scopes"`
|
||||||
|
OIDCIDPDisplayNameMapping int32 `json:"idpDisplayNameMapping" gorm:"column:oidc_idp_display_name_mapping"`
|
||||||
|
OIDCUsernameMapping int32 `json:"usernameMapping" gorm:"column:oidc_idp_username_mapping"`
|
||||||
|
|
||||||
Sequence uint64 `json:"-" gorm:"column:sequence"`
|
Sequence uint64 `json:"-" gorm:"column:sequence"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func IDPConfigViewFromModel(idp *model.IDPConfigView) *IDPConfigView {
|
func IDPConfigViewFromModel(idp *model.IDPConfigView) *IDPConfigView {
|
||||||
return &IDPConfigView{
|
return &IDPConfigView{
|
||||||
IDPConfigID: idp.IDPConfigID,
|
IDPConfigID: idp.IDPConfigID,
|
||||||
AggregateID: idp.AggregateID,
|
AggregateID: idp.AggregateID,
|
||||||
Name: idp.Name,
|
Name: idp.Name,
|
||||||
LogoSrc: idp.LogoSrc,
|
LogoSrc: idp.LogoSrc,
|
||||||
Sequence: idp.Sequence,
|
Sequence: idp.Sequence,
|
||||||
CreationDate: idp.CreationDate,
|
CreationDate: idp.CreationDate,
|
||||||
ChangeDate: idp.ChangeDate,
|
ChangeDate: idp.ChangeDate,
|
||||||
IDPProviderType: int32(idp.IDPProviderType),
|
IDPProviderType: int32(idp.IDPProviderType),
|
||||||
IsOIDC: idp.IsOIDC,
|
IsOIDC: idp.IsOIDC,
|
||||||
OIDCClientID: idp.OIDCClientID,
|
OIDCClientID: idp.OIDCClientID,
|
||||||
OIDCClientSecret: idp.OIDCClientSecret,
|
OIDCClientSecret: idp.OIDCClientSecret,
|
||||||
OIDCIssuer: idp.OIDCIssuer,
|
OIDCIssuer: idp.OIDCIssuer,
|
||||||
OIDCScopes: idp.OIDCScopes,
|
OIDCScopes: idp.OIDCScopes,
|
||||||
|
OIDCIDPDisplayNameMapping: int32(idp.OIDCIDPDisplayNameMapping),
|
||||||
|
OIDCUsernameMapping: int32(idp.OIDCUsernameMapping),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func IdpConfigViewToModel(idp *IDPConfigView) *model.IDPConfigView {
|
func IDPConfigViewToModel(idp *IDPConfigView) *model.IDPConfigView {
|
||||||
return &model.IDPConfigView{
|
return &model.IDPConfigView{
|
||||||
IDPConfigID: idp.IDPConfigID,
|
IDPConfigID: idp.IDPConfigID,
|
||||||
AggregateID: idp.AggregateID,
|
AggregateID: idp.AggregateID,
|
||||||
Name: idp.Name,
|
Name: idp.Name,
|
||||||
LogoSrc: idp.LogoSrc,
|
LogoSrc: idp.LogoSrc,
|
||||||
Sequence: idp.Sequence,
|
Sequence: idp.Sequence,
|
||||||
CreationDate: idp.CreationDate,
|
CreationDate: idp.CreationDate,
|
||||||
ChangeDate: idp.ChangeDate,
|
ChangeDate: idp.ChangeDate,
|
||||||
IDPProviderType: model.IDPProviderType(idp.IDPProviderType),
|
IDPProviderType: model.IDPProviderType(idp.IDPProviderType),
|
||||||
IsOIDC: idp.IsOIDC,
|
IsOIDC: idp.IsOIDC,
|
||||||
OIDCClientID: idp.OIDCClientID,
|
OIDCClientID: idp.OIDCClientID,
|
||||||
OIDCClientSecret: idp.OIDCClientSecret,
|
OIDCClientSecret: idp.OIDCClientSecret,
|
||||||
OIDCIssuer: idp.OIDCIssuer,
|
OIDCIssuer: idp.OIDCIssuer,
|
||||||
OIDCScopes: idp.OIDCScopes,
|
OIDCScopes: idp.OIDCScopes,
|
||||||
|
OIDCIDPDisplayNameMapping: model.OIDCMappingField(idp.OIDCIDPDisplayNameMapping),
|
||||||
|
OIDCUsernameMapping: model.OIDCMappingField(idp.OIDCUsernameMapping),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func IdpConfigViewsToModel(idps []*IDPConfigView) []*model.IDPConfigView {
|
func IdpConfigViewsToModel(idps []*IDPConfigView) []*model.IDPConfigView {
|
||||||
result := make([]*model.IDPConfigView, len(idps))
|
result := make([]*model.IDPConfigView, len(idps))
|
||||||
for i, idp := range idps {
|
for i, idp := range idps {
|
||||||
result[i] = IdpConfigViewToModel(idp)
|
result[i] = IDPConfigViewToModel(idp)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ func IDPProviderViewFromModel(policy *model.IDPProviderView) *IDPProviderView {
|
|||||||
CreationDate: policy.CreationDate,
|
CreationDate: policy.CreationDate,
|
||||||
ChangeDate: policy.ChangeDate,
|
ChangeDate: policy.ChangeDate,
|
||||||
Name: policy.Name,
|
Name: policy.Name,
|
||||||
|
IDPConfigID: policy.IDPConfigID,
|
||||||
IDPConfigType: int32(policy.IDPConfigType),
|
IDPConfigType: int32(policy.IDPConfigType),
|
||||||
IDPProviderType: int32(policy.IDPProviderType),
|
IDPProviderType: int32(policy.IDPProviderType),
|
||||||
}
|
}
|
||||||
@ -51,6 +52,7 @@ func IDPProviderViewToModel(policy *IDPProviderView) *model.IDPProviderView {
|
|||||||
CreationDate: policy.CreationDate,
|
CreationDate: policy.CreationDate,
|
||||||
ChangeDate: policy.ChangeDate,
|
ChangeDate: policy.ChangeDate,
|
||||||
Name: policy.Name,
|
Name: policy.Name,
|
||||||
|
IDPConfigID: policy.IDPConfigID,
|
||||||
IDPConfigType: model.IdpConfigType(policy.IDPConfigType),
|
IDPConfigType: model.IdpConfigType(policy.IDPConfigType),
|
||||||
IDPProviderType: model.IDPProviderType(policy.IDPProviderType),
|
IDPProviderType: model.IDPProviderType(policy.IDPProviderType),
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||||
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||||
|
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
@ -218,7 +219,7 @@ func (repo *OrgRepository) IDPConfigByID(ctx context.Context, idpConfigID string
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return iam_view_model.IdpConfigViewToModel(idp), nil
|
return iam_view_model.IDPConfigViewToModel(idp), nil
|
||||||
}
|
}
|
||||||
func (repo *OrgRepository) AddOIDCIDPConfig(ctx context.Context, idp *iam_model.IDPConfig) (*iam_model.IDPConfig, error) {
|
func (repo *OrgRepository) AddOIDCIDPConfig(ctx context.Context, idp *iam_model.IDPConfig) (*iam_model.IDPConfig, error) {
|
||||||
idp.AggregateID = authz.GetCtxData(ctx).OrgID
|
idp.AggregateID = authz.GetCtxData(ctx).OrgID
|
||||||
@ -239,8 +240,27 @@ func (repo *OrgRepository) ReactivateIDPConfig(ctx context.Context, idpConfigID
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (repo *OrgRepository) RemoveIDPConfig(ctx context.Context, idpConfigID string) error {
|
func (repo *OrgRepository) RemoveIDPConfig(ctx context.Context, idpConfigID string) error {
|
||||||
|
aggregates := make([]*es_models.Aggregate, 0)
|
||||||
idp := iam_model.NewIDPConfig(authz.GetCtxData(ctx).OrgID, idpConfigID)
|
idp := iam_model.NewIDPConfig(authz.GetCtxData(ctx).OrgID, idpConfigID)
|
||||||
return repo.OrgEventstore.RemoveIDPConfig(ctx, idp)
|
_, agg, err := repo.OrgEventstore.PrepareRemoveIDPConfig(ctx, idp)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
}
|
||||||
|
aggregates = append(aggregates, agg)
|
||||||
|
externalIDPs, err := repo.View.ExternalIDPsByIDPConfigID(idpConfigID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, externalIDP := range externalIDPs {
|
||||||
|
idpRemove := &usr_model.ExternalIDP{ObjectRoot: es_models.ObjectRoot{AggregateID: externalIDP.UserID}, IDPConfigID: externalIDP.IDPConfigID, UserID: externalIDP.ExternalUserID}
|
||||||
|
idpAgg := make([]*es_models.Aggregate, 0)
|
||||||
|
_, idpAgg, err = repo.UserEvents.PrepareRemoveExternalIDP(ctx, idpRemove, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
aggregates = append(aggregates, idpAgg...)
|
||||||
|
}
|
||||||
|
return sdk.PushAggregates(ctx, repo.Eventstore.PushAggregates, nil, aggregates...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *OrgRepository) ChangeOIDCIDPConfig(ctx context.Context, oidcConfig *iam_model.OIDCIDPConfig) (*iam_model.OIDCIDPConfig, error) {
|
func (repo *OrgRepository) ChangeOIDCIDPConfig(ctx context.Context, oidcConfig *iam_model.OIDCIDPConfig) (*iam_model.OIDCIDPConfig, error) {
|
||||||
@ -338,6 +358,25 @@ func (repo *OrgRepository) AddIDPProviderToLoginPolicy(ctx context.Context, prov
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (repo *OrgRepository) RemoveIDPProviderFromIdpProvider(ctx context.Context, provider *iam_model.IDPProvider) error {
|
func (repo *OrgRepository) RemoveIDPProviderFromIdpProvider(ctx context.Context, provider *iam_model.IDPProvider) error {
|
||||||
|
aggregates := make([]*es_models.Aggregate, 0)
|
||||||
provider.AggregateID = authz.GetCtxData(ctx).OrgID
|
provider.AggregateID = authz.GetCtxData(ctx).OrgID
|
||||||
return repo.OrgEventstore.RemoveIDPProviderFromLoginPolicy(ctx, provider)
|
_, agg, err := repo.OrgEventstore.PrepareRemoveIDPProviderFromLoginPolicy(ctx, provider, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
aggregates = append(aggregates, agg)
|
||||||
|
externalIDPs, err := repo.View.ExternalIDPsByIDPConfigID(provider.IdpConfigID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, externalIDP := range externalIDPs {
|
||||||
|
idpRemove := &usr_model.ExternalIDP{ObjectRoot: es_models.ObjectRoot{AggregateID: externalIDP.UserID}, IDPConfigID: externalIDP.IDPConfigID, UserID: externalIDP.ExternalUserID}
|
||||||
|
idpAgg := make([]*es_models.Aggregate, 0)
|
||||||
|
_, idpAgg, err = repo.UserEvents.PrepareRemoveExternalIDP(ctx, idpRemove, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
aggregates = append(aggregates, idpAgg...)
|
||||||
|
}
|
||||||
|
return sdk.PushAggregates(ctx, repo.Eventstore.PushAggregates, nil, aggregates...)
|
||||||
}
|
}
|
||||||
|
@ -181,6 +181,31 @@ func (repo *UserRepo) ProfileByID(ctx context.Context, userID string) (*usr_mode
|
|||||||
return user.GetProfile()
|
return user.GetProfile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) SearchExternalIDPs(ctx context.Context, request *usr_model.ExternalIDPSearchRequest) (*usr_model.ExternalIDPSearchResponse, error) {
|
||||||
|
request.EnsureLimit(repo.SearchLimit)
|
||||||
|
sequence, seqErr := repo.View.GetLatestExternalIDPSequence()
|
||||||
|
logging.Log("EVENT-Qs7uf").OnError(seqErr).Warn("could not read latest external idp sequence")
|
||||||
|
externalIDPS, count, err := repo.View.SearchExternalIDPs(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &usr_model.ExternalIDPSearchResponse{
|
||||||
|
Offset: request.Offset,
|
||||||
|
Limit: request.Limit,
|
||||||
|
TotalResult: count,
|
||||||
|
Result: model.ExternalIDPViewsToModel(externalIDPS),
|
||||||
|
}
|
||||||
|
if seqErr == nil {
|
||||||
|
result.Sequence = sequence.CurrentSequence
|
||||||
|
result.Timestamp = sequence.CurrentTimestamp
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) RemoveExternalIDP(ctx context.Context, externalIDP *usr_model.ExternalIDP) error {
|
||||||
|
return repo.UserEvents.RemoveExternalIDP(ctx, externalIDP)
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *UserRepo) ChangeMachine(ctx context.Context, machine *usr_model.Machine) (*usr_model.Machine, error) {
|
func (repo *UserRepo) ChangeMachine(ctx context.Context, machine *usr_model.Machine) (*usr_model.Machine, error) {
|
||||||
return repo.UserEvents.ChangeMachine(ctx, machine)
|
return repo.UserEvents.ChangeMachine(ctx, machine)
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, ev
|
|||||||
&IDPConfig{handler: handler{view, bulkLimit, configs.cycleDuration("IDPConfig"), errorCount}},
|
&IDPConfig{handler: handler{view, bulkLimit, configs.cycleDuration("IDPConfig"), errorCount}},
|
||||||
&LoginPolicy{handler: handler{view, bulkLimit, configs.cycleDuration("LoginPolicy"), errorCount}},
|
&LoginPolicy{handler: handler{view, bulkLimit, configs.cycleDuration("LoginPolicy"), errorCount}},
|
||||||
&IDPProvider{handler: handler{view, bulkLimit, configs.cycleDuration("IDPProvider"), errorCount}, systemDefaults: defaults, iamEvents: repos.IamEvents, orgEvents: repos.OrgEvents},
|
&IDPProvider{handler: handler{view, bulkLimit, configs.cycleDuration("IDPProvider"), errorCount}, systemDefaults: defaults, iamEvents: repos.IamEvents, orgEvents: repos.OrgEvents},
|
||||||
|
&ExternalIDP{handler: handler{view, bulkLimit, configs.cycleDuration("ExternalIDP"), errorCount}, systemDefaults: defaults, iamEvents: repos.IamEvents, orgEvents: repos.OrgEvents},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,126 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/caos/zitadel/internal/iam/repository/eventsourcing"
|
||||||
|
org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||||
|
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||||
|
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||||
|
usr_view_model "github.com/caos/zitadel/internal/user/repository/view/model"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||||
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
|
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExternalIDP struct {
|
||||||
|
handler
|
||||||
|
systemDefaults systemdefaults.SystemDefaults
|
||||||
|
iamEvents *eventsourcing.IAMEventstore
|
||||||
|
orgEvents *org_es.OrgEventstore
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
externalIDPTable = "management.user_external_idps"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *ExternalIDP) ViewModel() string {
|
||||||
|
return externalIDPTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) EventQuery() (*models.SearchQuery, error) {
|
||||||
|
sequence, err := m.view.GetLatestExternalIDPSequence()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return es_models.NewSearchQuery().
|
||||||
|
AggregateTypeFilter(model.UserAggregate, iam_es_model.IAMAggregate, org_es_model.OrgAggregate).
|
||||||
|
LatestSequenceFilter(sequence.CurrentSequence), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) Reduce(event *models.Event) (err error) {
|
||||||
|
switch event.AggregateType {
|
||||||
|
case model.UserAggregate:
|
||||||
|
err = m.processUser(event)
|
||||||
|
case iam_es_model.IAMAggregate, org_es_model.OrgAggregate:
|
||||||
|
err = m.processIdpConfig(event)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) processUser(event *models.Event) (err error) {
|
||||||
|
externalIDP := new(usr_view_model.ExternalIDPView)
|
||||||
|
switch event.Type {
|
||||||
|
case model.HumanExternalIDPAdded:
|
||||||
|
err = externalIDP.AppendEvent(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = m.fillData(externalIDP)
|
||||||
|
case model.HumanExternalIDPRemoved, model.HumanExternalIDPCascadeRemoved:
|
||||||
|
err = externalIDP.SetData(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return m.view.DeleteExternalIDP(externalIDP.ExternalUserID, externalIDP.IDPConfigID, event.Sequence)
|
||||||
|
default:
|
||||||
|
return m.view.ProcessedExternalIDPSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return m.view.PutExternalIDP(externalIDP, externalIDP.Sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) processIdpConfig(event *models.Event) (err error) {
|
||||||
|
switch event.Type {
|
||||||
|
case iam_es_model.IDPConfigChanged, org_es_model.IDPConfigChanged:
|
||||||
|
config := new(iam_model.IDPConfig)
|
||||||
|
config.AppendEvent(event)
|
||||||
|
exterinalIDPs, err := m.view.ExternalIDPsByIDPConfigID(config.IDPConfigID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if event.AggregateType == iam_es_model.IAMAggregate {
|
||||||
|
config, err = m.iamEvents.GetIDPConfig(context.Background(), config.AggregateID, config.IDPConfigID)
|
||||||
|
} else {
|
||||||
|
config, err = m.orgEvents.GetIDPConfig(context.Background(), config.AggregateID, config.IDPConfigID)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, provider := range exterinalIDPs {
|
||||||
|
m.fillConfigData(provider, config)
|
||||||
|
}
|
||||||
|
return m.view.PutExternalIDPs(event.Sequence, exterinalIDPs...)
|
||||||
|
default:
|
||||||
|
return m.view.ProcessedExternalIDPSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) fillData(externalIDP *usr_view_model.ExternalIDPView) error {
|
||||||
|
config, err := m.orgEvents.GetIDPConfig(context.Background(), externalIDP.ResourceOwner, externalIDP.IDPConfigID)
|
||||||
|
if caos_errs.IsNotFound(err) {
|
||||||
|
config, err = m.iamEvents.GetIDPConfig(context.Background(), m.systemDefaults.IamID, externalIDP.IDPConfigID)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.fillConfigData(externalIDP, config)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) fillConfigData(externalIDP *usr_view_model.ExternalIDPView, config *iam_model.IDPConfig) {
|
||||||
|
externalIDP.IDPName = config.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ExternalIDP) OnError(event *models.Event, err error) error {
|
||||||
|
logging.LogWithFields("SPOOL-4Rsu8", "id", event.AggregateID).WithError(err).Warn("something went wrong in idp provider handler")
|
||||||
|
return spooler.HandleError(event, err, m.view.GetLatestExternalIDPFailedEvent, m.view.ProcessedExternalIDPFailedEvent, m.view.ProcessedExternalIDPSequence, m.errorCountUntilSkip)
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||||
|
"github.com/caos/zitadel/internal/user/repository/view"
|
||||||
|
"github.com/caos/zitadel/internal/user/repository/view/model"
|
||||||
|
global_view "github.com/caos/zitadel/internal/view/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
externalIDPTable = "management.user_external_idps"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *View) ExternalIDPByExternalUserIDAndIDPConfigID(externalUserID, idpConfigID string) (*model.ExternalIDPView, error) {
|
||||||
|
return view.ExternalIDPByExternalUserIDAndIDPConfigID(v.Db, externalIDPTable, externalUserID, idpConfigID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ExternalIDPByExternalUserIDAndIDPConfigIDAndResourceOwner(externalUserID, idpConfigID, resourceOwner string) (*model.ExternalIDPView, error) {
|
||||||
|
return view.ExternalIDPByExternalUserIDAndIDPConfigIDAndResourceOwner(v.Db, externalIDPTable, externalUserID, idpConfigID, resourceOwner)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ExternalIDPsByIDPConfigID(idpConfigID string) ([]*model.ExternalIDPView, error) {
|
||||||
|
return view.ExternalIDPsByIDPConfigID(v.Db, externalIDPTable, idpConfigID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ExternalIDPsByUserID(userID string) ([]*model.ExternalIDPView, error) {
|
||||||
|
return view.ExternalIDPsByUserID(v.Db, externalIDPTable, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) SearchExternalIDPs(request *usr_model.ExternalIDPSearchRequest) ([]*model.ExternalIDPView, uint64, error) {
|
||||||
|
return view.SearchExternalIDPs(v.Db, externalIDPTable, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) PutExternalIDP(externalIDP *model.ExternalIDPView, sequence uint64) error {
|
||||||
|
err := view.PutExternalIDP(v.Db, externalIDPTable, externalIDP)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedExternalIDPSequence(sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) PutExternalIDPs(sequence uint64, externalIDPs ...*model.ExternalIDPView) error {
|
||||||
|
err := view.PutExternalIDPs(v.Db, externalIDPTable, externalIDPs...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedExternalIDPSequence(sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) DeleteExternalIDP(externalUserID, idpConfigID string, eventSequence uint64) error {
|
||||||
|
err := view.DeleteExternalIDP(v.Db, externalIDPTable, externalUserID, idpConfigID)
|
||||||
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedExternalIDPSequence(eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestExternalIDPSequence() (*global_view.CurrentSequence, error) {
|
||||||
|
return v.latestSequence(externalIDPTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedExternalIDPSequence(eventSequence uint64) error {
|
||||||
|
return v.saveCurrentSequence(externalIDPTable, eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestExternalIDPFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||||
|
return v.latestFailedEvent(externalIDPTable, sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedExternalIDPFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||||
|
return v.saveFailedEvent(failedEvent)
|
||||||
|
}
|
@ -31,6 +31,9 @@ type UserRepository interface {
|
|||||||
|
|
||||||
UserMfas(ctx context.Context, userID string) ([]*model.MultiFactor, error)
|
UserMfas(ctx context.Context, userID string) ([]*model.MultiFactor, error)
|
||||||
|
|
||||||
|
SearchExternalIDPs(ctx context.Context, request *model.ExternalIDPSearchRequest) (*model.ExternalIDPSearchResponse, error)
|
||||||
|
RemoveExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) error
|
||||||
|
|
||||||
SearchMachineKeys(ctx context.Context, request *model.MachineKeySearchRequest) (*model.MachineKeySearchResponse, error)
|
SearchMachineKeys(ctx context.Context, request *model.MachineKeySearchRequest) (*model.MachineKeySearchResponse, error)
|
||||||
GetMachineKey(ctx context.Context, userID, keyID string) (*model.MachineKeyView, error)
|
GetMachineKey(ctx context.Context, userID, keyID string) (*model.MachineKeyView, error)
|
||||||
ChangeMachine(ctx context.Context, machine *model.Machine) (*model.Machine, error)
|
ChangeMachine(ctx context.Context, machine *model.Machine) (*model.Machine, error)
|
||||||
|
@ -464,6 +464,10 @@ func (es *OrgEventstore) RemoveOrgMember(ctx context.Context, member *org_model.
|
|||||||
return es_sdk.Push(ctx, es.PushAggregates, repoMember.AppendEvents, orgAggregate)
|
return es_sdk.Push(ctx, es.PushAggregates, repoMember.AppendEvents, orgAggregate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (es *OrgEventstore) GetDefaultOrgIAMPolicy(ctx context.Context) *org_model.OrgIAMPolicy {
|
||||||
|
return es.defaultOrgIamPolicy
|
||||||
|
}
|
||||||
|
|
||||||
func (es *OrgEventstore) GetOrgIAMPolicy(ctx context.Context, orgID string) (*org_model.OrgIAMPolicy, error) {
|
func (es *OrgEventstore) GetOrgIAMPolicy(ctx context.Context, orgID string) (*org_model.OrgIAMPolicy, error) {
|
||||||
existingOrg, err := es.OrgByID(ctx, org_model.NewOrg(orgID))
|
existingOrg, err := es.OrgByID(ctx, org_model.NewOrg(orgID))
|
||||||
if err != nil && !errors.IsNotFound(err) {
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
|
@ -386,7 +386,9 @@ func IDPConfigRemovedAggregate(ctx context.Context, aggCreator *es_models.Aggreg
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
agg, err = agg.AppendEvent(model.IDPConfigRemoved, &iam_es_model.IDPConfigID{IDPConfigID: idp.IDPConfigID})
|
agg, err = agg.AppendEvent(model.IDPConfigRemoved, &iam_es_model.IDPConfigID{IDPConfigID: idp.IDPConfigID})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ Errors:
|
|||||||
AddressNotFound: Addresse nicht gefunden
|
AddressNotFound: Addresse nicht gefunden
|
||||||
NotHuman: Der Benutzer muss eine Person sein
|
NotHuman: Der Benutzer muss eine Person sein
|
||||||
NotMachine: Der Benutzer muss technisch sein
|
NotMachine: Der Benutzer muss technisch sein
|
||||||
|
NotAllowedToLink: Der Benutzer darf nicht mit einem externen Login Provider verlinkt werden
|
||||||
Username:
|
Username:
|
||||||
Reservied: Benutzername ist bereits vergeben
|
Reservied: Benutzername ist bereits vergeben
|
||||||
Code:
|
Code:
|
||||||
@ -45,6 +46,11 @@ Errors:
|
|||||||
HasUpper: Passwort beinhaltet keinen Grossbuchstaben
|
HasUpper: Passwort beinhaltet keinen Grossbuchstaben
|
||||||
HasNumber: Passwort beinhaltet keine Nummer
|
HasNumber: Passwort beinhaltet keine Nummer
|
||||||
HasSymbol: Passwort beinhaltet kein Symbol
|
HasSymbol: Passwort beinhaltet kein Symbol
|
||||||
|
ExternalIDP:
|
||||||
|
Invalid: Externer IDP ungültig
|
||||||
|
IDPConfigNotExisting: IDP Provider ungültig für diese Organisation
|
||||||
|
NotAllowed: Externer IDP ist auf dieser Organisation nicht erlaubt.
|
||||||
|
MinimumExternalIDPNeeded: Mindestens ein IDP muss hinzugefügt werden.
|
||||||
Mfa:
|
Mfa:
|
||||||
Otp:
|
Otp:
|
||||||
AlreadyReady: Multifaktor OTP (OneTimePassword) ist bereits eingerichtet
|
AlreadyReady: Multifaktor OTP (OneTimePassword) ist bereits eingerichtet
|
||||||
|
@ -26,6 +26,7 @@ Errors:
|
|||||||
AddressNotFound: Address not found
|
AddressNotFound: Address not found
|
||||||
NotHuman: The User must be personal
|
NotHuman: The User must be personal
|
||||||
NotMachine: The User must be technical
|
NotMachine: The User must be technical
|
||||||
|
NotAllowedToLink: User is not allowed to link with external login provider
|
||||||
Username:
|
Username:
|
||||||
Reservied: Username is already taken
|
Reservied: Username is already taken
|
||||||
Code:
|
Code:
|
||||||
@ -45,6 +46,11 @@ Errors:
|
|||||||
HasUpper: Password must contain upper case
|
HasUpper: Password must contain upper case
|
||||||
HasNumber: Password must contain number
|
HasNumber: Password must contain number
|
||||||
HasSymbol: Password must contain symbol
|
HasSymbol: Password must contain symbol
|
||||||
|
ExternalIDP:
|
||||||
|
Invalid: Externer IDP invalid
|
||||||
|
IDPConfigNotExisting: IDP provider invalid for this organisation
|
||||||
|
NotAllowed: External IDP not allowed on this organisation
|
||||||
|
MinimumExternalIDPNeeded: At least one IDP must be added
|
||||||
Mfa:
|
Mfa:
|
||||||
Otp:
|
Otp:
|
||||||
AlreadyReady: Multifactor OTP (OneTimePassword) is already set up
|
AlreadyReady: Multifactor OTP (OneTimePassword) is already set up
|
||||||
@ -97,7 +103,7 @@ Errors:
|
|||||||
MemberInvalid: Project member is invalid
|
MemberInvalid: Project member is invalid
|
||||||
MemberAlreadyExists: Project member already exists
|
MemberAlreadyExists: Project member already exists
|
||||||
MemberNotExisting: Project member doesn't exist
|
MemberNotExisting: Project member doesn't exist
|
||||||
MinimumOneRoleNeeded: At least one role should be added
|
MinimumOneRoleNeeded: At least one role must be added
|
||||||
RoleAlreadyExists: Role already exists
|
RoleAlreadyExists: Role already exists
|
||||||
RoleInvalid: Role is invalid
|
RoleInvalid: Role is invalid
|
||||||
RoleNotExisting: Role doesn't exist
|
RoleNotExisting: Role doesn't exist
|
||||||
|
@ -28,3 +28,7 @@ func (l *Login) getAuthRequestAndParseData(r *http.Request, data interface{}) (*
|
|||||||
err = l.parser.Parse(r, data)
|
err = l.parser.Parse(r, data)
|
||||||
return authReq, err
|
return authReq, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Login) getParseData(r *http.Request, data interface{}) error {
|
||||||
|
return l.parser.Parse(r, data)
|
||||||
|
}
|
||||||
|
269
internal/ui/login/handler/external_login_handler.go
Normal file
269
internal/ui/login/handler/external_login_handler.go
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/oidc/pkg/oidc"
|
||||||
|
"github.com/caos/oidc/pkg/rp"
|
||||||
|
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
|
||||||
|
"github.com/caos/zitadel/internal/auth_request/model"
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
|
caos_errors "github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
|
org_model "github.com/caos/zitadel/internal/org/model"
|
||||||
|
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
queryIDPConfigID = "idpConfigID"
|
||||||
|
tmplExternalNotFoundOption = "externalnotfoundoption"
|
||||||
|
)
|
||||||
|
|
||||||
|
type externalIDPData struct {
|
||||||
|
IDPConfigID string `schema:"idpConfigID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type externalIDPCallbackData struct {
|
||||||
|
State string `schema:"state"`
|
||||||
|
Code string `schema:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type externalNotFoundOptionFormData struct {
|
||||||
|
Link bool `schema:"link"`
|
||||||
|
AutoRegister bool `schema:"autoregister"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type externalNotFoundOptionData struct {
|
||||||
|
baseData
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) handleExternalLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := new(externalIDPData)
|
||||||
|
authReq, err := l.getAuthRequestAndParseData(r, data)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if authReq == nil {
|
||||||
|
http.Redirect(w, r, l.zitadelURL, http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
idpConfig, err := l.getIDPConfigByID(r, data.IDPConfigID)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
||||||
|
err = l.authRepo.SelectExternalIDP(r.Context(), authReq.ID, idpConfig.IDPConfigID, userAgentID)
|
||||||
|
if err != nil {
|
||||||
|
l.renderLogin(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !idpConfig.IsOIDC {
|
||||||
|
l.renderError(w, r, authReq, caos_errors.ThrowInternal(nil, "LOGIN-Rio9s", "Errors.User.ExternalIDP.IDPTypeNotImplemented"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.handleOIDCAuthorize(w, r, authReq, idpConfig, EndpointExternalLoginCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) handleOIDCAuthorize(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, idpConfig *iam_model.IDPConfigView, callbackEndpoint string) {
|
||||||
|
provider := l.getRPConfig(w, r, authReq, idpConfig, callbackEndpoint)
|
||||||
|
http.Redirect(w, r, rp.AuthURL(authReq.ID, provider), http.StatusFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := new(externalIDPCallbackData)
|
||||||
|
err := l.getParseData(r, data)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
||||||
|
authReq, err := l.authRepo.AuthRequestByID(r.Context(), data.State, userAgentID)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
idpConfig, err := l.authRepo.GetIDPConfigByID(r.Context(), authReq.SelectedIDPConfigID)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
provider := l.getRPConfig(w, r, authReq, idpConfig, EndpointExternalLoginCallback)
|
||||||
|
tokens, err := rp.CodeExchange(r.Context(), data.Code, provider)
|
||||||
|
if err != nil {
|
||||||
|
l.renderLogin(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.handleExternalUserAuthenticated(w, r, authReq, idpConfig, userAgentID, tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) getRPConfig(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, idpConfig *iam_model.IDPConfigView, callbackEndpoint string) rp.RelayingParty {
|
||||||
|
oidcClientSecret, err := crypto.DecryptString(idpConfig.OIDCClientSecret, l.IDPConfigAesCrypto)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
provider, err := rp.NewRelayingPartyOIDC(idpConfig.OIDCIssuer, idpConfig.OIDCClientID, oidcClientSecret, l.baseURL+callbackEndpoint, idpConfig.OIDCScopes, rp.WithVerifierOpts(rp.WithIssuedAtOffset(3*time.Second)))
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) handleExternalUserAuthenticated(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, idpConfig *iam_model.IDPConfigView, userAgentID string, tokens *oidc.Tokens) {
|
||||||
|
externalUser := l.mapTokenToLoginUser(tokens, idpConfig)
|
||||||
|
err := l.authRepo.CheckExternalUserLogin(r.Context(), authReq.ID, userAgentID, externalUser)
|
||||||
|
if err != nil {
|
||||||
|
l.renderExternalNotFoundOption(w, r, authReq, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.renderNextStep(w, r, authReq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) renderExternalNotFoundOption(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) {
|
||||||
|
var errType, errMessage string
|
||||||
|
if err != nil {
|
||||||
|
errMessage = l.getErrorMessage(r, err)
|
||||||
|
}
|
||||||
|
data := externalNotFoundOptionData{
|
||||||
|
baseData: l.getBaseData(r, authReq, "ExternalNotFoundOption", errType, errMessage),
|
||||||
|
}
|
||||||
|
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplExternalNotFoundOption], data, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) handleExternalNotFoundOptionCheck(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := new(externalNotFoundOptionFormData)
|
||||||
|
authReq, err := l.getAuthRequestAndParseData(r, data)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if data.Link {
|
||||||
|
l.renderLogin(w, r, authReq, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.handleAutoRegister(w, r, authReq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) {
|
||||||
|
orgIamPolicy, err := l.getOrgIamPolicy(r, authReq.GetScopeOrgID())
|
||||||
|
if err != nil {
|
||||||
|
l.renderExternalNotFoundOption(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
iam, err := l.authRepo.GetIAM(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
l.renderExternalNotFoundOption(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resourceOwner := iam.GlobalOrgID
|
||||||
|
member := &org_model.OrgMember{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: iam.GlobalOrgID},
|
||||||
|
Roles: []string{orgProjectCreatorRole},
|
||||||
|
}
|
||||||
|
if authReq.GetScopeOrgID() != iam.GlobalOrgID && authReq.GetScopeOrgID() != "" {
|
||||||
|
member = nil
|
||||||
|
resourceOwner = authReq.GetScopeOrgID()
|
||||||
|
}
|
||||||
|
|
||||||
|
idpConfig, err := l.authRepo.GetIDPConfigByID(r.Context(), authReq.SelectedIDPConfigID)
|
||||||
|
if err != nil {
|
||||||
|
l.renderExternalNotFoundOption(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
||||||
|
user, externalIDP := l.mapExternalUserToLoginUser(orgIamPolicy, authReq.LinkingUsers[len(authReq.LinkingUsers)-1], idpConfig)
|
||||||
|
err = l.authRepo.AutoRegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, member, authReq.ID, userAgentID, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
l.renderExternalNotFoundOption(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.renderNextStep(w, r, authReq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) mapTokenToLoginUser(tokens *oidc.Tokens, idpConfig *iam_model.IDPConfigView) *model.ExternalUser {
|
||||||
|
displayName := tokens.IDTokenClaims.PreferredUsername
|
||||||
|
switch idpConfig.OIDCIDPDisplayNameMapping {
|
||||||
|
case iam_model.OIDCMappingFieldEmail:
|
||||||
|
if tokens.IDTokenClaims.EmailVerified && tokens.IDTokenClaims.Email != "" {
|
||||||
|
displayName = tokens.IDTokenClaims.Email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
externalUser := &model.ExternalUser{
|
||||||
|
IDPConfigID: idpConfig.IDPConfigID,
|
||||||
|
ExternalUserID: tokens.IDTokenClaims.Subject,
|
||||||
|
PreferredUsername: tokens.IDTokenClaims.PreferredUsername,
|
||||||
|
DisplayName: displayName,
|
||||||
|
FirstName: tokens.IDTokenClaims.GivenName,
|
||||||
|
LastName: tokens.IDTokenClaims.FamilyName,
|
||||||
|
NickName: tokens.IDTokenClaims.Nickname,
|
||||||
|
Email: tokens.IDTokenClaims.Email,
|
||||||
|
IsEmailVerified: tokens.IDTokenClaims.EmailVerified,
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokens.IDTokenClaims.PhoneNumber != "" {
|
||||||
|
externalUser.Phone = tokens.IDTokenClaims.PhoneNumber
|
||||||
|
externalUser.IsPhoneVerified = tokens.IDTokenClaims.PhoneNumberVerified
|
||||||
|
}
|
||||||
|
return externalUser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) mapExternalUserToLoginUser(orgIamPolicy *org_model.OrgIAMPolicy, linkingUser *model.ExternalUser, idpConfig *iam_model.IDPConfigView) (*usr_model.User, *usr_model.ExternalIDP) {
|
||||||
|
username := linkingUser.PreferredUsername
|
||||||
|
switch idpConfig.OIDCUsernameMapping {
|
||||||
|
case iam_model.OIDCMappingFieldEmail:
|
||||||
|
if linkingUser.IsEmailVerified && linkingUser.Email != "" {
|
||||||
|
username = linkingUser.Email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if orgIamPolicy.UserLoginMustBeDomain {
|
||||||
|
splittedUsername := strings.Split(username, "@")
|
||||||
|
if len(splittedUsername) > 1 {
|
||||||
|
username = splittedUsername[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &usr_model.User{
|
||||||
|
UserName: username,
|
||||||
|
Human: &usr_model.Human{
|
||||||
|
Profile: &usr_model.Profile{
|
||||||
|
FirstName: linkingUser.FirstName,
|
||||||
|
LastName: linkingUser.LastName,
|
||||||
|
PreferredLanguage: linkingUser.PreferredLanguage,
|
||||||
|
NickName: linkingUser.NickName,
|
||||||
|
},
|
||||||
|
Email: &usr_model.Email{
|
||||||
|
EmailAddress: linkingUser.Email,
|
||||||
|
IsEmailVerified: linkingUser.IsEmailVerified,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if linkingUser.Phone != "" {
|
||||||
|
user.Phone = &usr_model.Phone{
|
||||||
|
PhoneNumber: linkingUser.Phone,
|
||||||
|
IsPhoneVerified: linkingUser.IsPhoneVerified,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
displayName := linkingUser.PreferredUsername
|
||||||
|
switch idpConfig.OIDCIDPDisplayNameMapping {
|
||||||
|
case iam_model.OIDCMappingFieldEmail:
|
||||||
|
if linkingUser.IsEmailVerified && linkingUser.Email != "" {
|
||||||
|
displayName = linkingUser.Email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
externalIDP := &usr_model.ExternalIDP{
|
||||||
|
IDPConfigID: idpConfig.IDPConfigID,
|
||||||
|
UserID: linkingUser.ExternalUserID,
|
||||||
|
DisplayName: displayName,
|
||||||
|
}
|
||||||
|
return user, externalIDP
|
||||||
|
}
|
155
internal/ui/login/handler/external_register_handler.go
Normal file
155
internal/ui/login/handler/external_register_handler.go
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/oidc/pkg/oidc"
|
||||||
|
"github.com/caos/oidc/pkg/rp"
|
||||||
|
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
|
||||||
|
"github.com/caos/zitadel/internal/auth_request/model"
|
||||||
|
caos_errors "github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
|
org_model "github.com/caos/zitadel/internal/org/model"
|
||||||
|
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l *Login) handleExternalRegister(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := new(externalIDPData)
|
||||||
|
authReq, err := l.getAuthRequestAndParseData(r, data)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if authReq == nil {
|
||||||
|
http.Redirect(w, r, l.zitadelURL, http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
idpConfig, err := l.getIDPConfigByID(r, data.IDPConfigID)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
||||||
|
err = l.authRepo.SelectExternalIDP(r.Context(), authReq.ID, idpConfig.IDPConfigID, userAgentID)
|
||||||
|
if err != nil {
|
||||||
|
l.renderLogin(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !idpConfig.IsOIDC {
|
||||||
|
l.renderError(w, r, authReq, caos_errors.ThrowInternal(nil, "LOGIN-Rio9s", "Errors.User.ExternalIDP.IDPTypeNotImplemented"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.handleOIDCAuthorize(w, r, authReq, idpConfig, EndpointExternalRegisterCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) handleExternalRegisterCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := new(externalIDPCallbackData)
|
||||||
|
err := l.getParseData(r, data)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
||||||
|
authReq, err := l.authRepo.AuthRequestByID(r.Context(), data.State, userAgentID)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
idpConfig, err := l.authRepo.GetIDPConfigByID(r.Context(), authReq.SelectedIDPConfigID)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
provider := l.getRPConfig(w, r, authReq, idpConfig, EndpointExternalRegisterCallback)
|
||||||
|
tokens, err := rp.CodeExchange(r.Context(), data.Code, provider)
|
||||||
|
if err != nil {
|
||||||
|
l.renderRegisterOption(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.handleExternalUserRegister(w, r, authReq, idpConfig, userAgentID, tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) handleExternalUserRegister(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, idpConfig *iam_model.IDPConfigView, userAgentID string, tokens *oidc.Tokens) {
|
||||||
|
orgIamPolicy, err := l.getOrgIamPolicy(r, authReq.GetScopeOrgID())
|
||||||
|
if err != nil {
|
||||||
|
l.renderRegisterOption(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
iam, err := l.authRepo.GetIAM(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
l.renderRegisterOption(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resourceOwner := iam.GlobalOrgID
|
||||||
|
member := &org_model.OrgMember{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: iam.GlobalOrgID},
|
||||||
|
Roles: []string{orgProjectCreatorRole},
|
||||||
|
}
|
||||||
|
if authReq.GetScopeOrgID() != iam.GlobalOrgID && authReq.GetScopeOrgID() != "" {
|
||||||
|
member = nil
|
||||||
|
resourceOwner = authReq.GetScopeOrgID()
|
||||||
|
}
|
||||||
|
|
||||||
|
user, externalIDP := l.mapTokenToLoginUserAndExternalIDP(orgIamPolicy, tokens, idpConfig)
|
||||||
|
_, err = l.authRepo.RegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, member, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
l.renderRegisterOption(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.renderNextStep(w, r, authReq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) mapTokenToLoginUserAndExternalIDP(orgIamPolicy *org_model.OrgIAMPolicy, tokens *oidc.Tokens, idpConfig *iam_model.IDPConfigView) (*usr_model.User, *usr_model.ExternalIDP) {
|
||||||
|
username := tokens.IDTokenClaims.PreferredUsername
|
||||||
|
switch idpConfig.OIDCUsernameMapping {
|
||||||
|
case iam_model.OIDCMappingFieldEmail:
|
||||||
|
if tokens.IDTokenClaims.EmailVerified && tokens.IDTokenClaims.Email != "" {
|
||||||
|
username = tokens.IDTokenClaims.Email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if orgIamPolicy.UserLoginMustBeDomain {
|
||||||
|
splittedUsername := strings.Split(username, "@")
|
||||||
|
if len(splittedUsername) > 1 {
|
||||||
|
username = splittedUsername[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &usr_model.User{
|
||||||
|
UserName: username,
|
||||||
|
Human: &usr_model.Human{
|
||||||
|
Profile: &usr_model.Profile{
|
||||||
|
FirstName: tokens.IDTokenClaims.GivenName,
|
||||||
|
LastName: tokens.IDTokenClaims.FamilyName,
|
||||||
|
PreferredLanguage: tokens.IDTokenClaims.Locale,
|
||||||
|
NickName: tokens.IDTokenClaims.Nickname,
|
||||||
|
},
|
||||||
|
Email: &usr_model.Email{
|
||||||
|
EmailAddress: tokens.IDTokenClaims.Email,
|
||||||
|
IsEmailVerified: tokens.IDTokenClaims.EmailVerified,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if tokens.IDTokenClaims.PhoneNumber != "" {
|
||||||
|
user.Phone = &usr_model.Phone{
|
||||||
|
PhoneNumber: tokens.IDTokenClaims.PhoneNumber,
|
||||||
|
IsPhoneVerified: tokens.IDTokenClaims.PhoneNumberVerified,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
displayName := tokens.IDTokenClaims.PreferredUsername
|
||||||
|
switch idpConfig.OIDCIDPDisplayNameMapping {
|
||||||
|
case iam_model.OIDCMappingFieldEmail:
|
||||||
|
if tokens.IDTokenClaims.EmailVerified && tokens.IDTokenClaims.Email != "" {
|
||||||
|
displayName = tokens.IDTokenClaims.Email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
externalIDP := &usr_model.ExternalIDP{
|
||||||
|
IDPConfigID: idpConfig.IDPConfigID,
|
||||||
|
UserID: tokens.IDTokenClaims.Subject,
|
||||||
|
DisplayName: displayName,
|
||||||
|
}
|
||||||
|
return user, externalIDP
|
||||||
|
}
|
24
internal/ui/login/handler/link_users_handler.go
Normal file
24
internal/ui/login/handler/link_users_handler.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/auth_request/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tmplLinkUsersDone = "linkusersdone"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l *Login) linkUsers(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) {
|
||||||
|
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
||||||
|
err = l.authRepo.LinkExternalUsers(setContext(r.Context(), authReq.UserOrgID), authReq.ID, userAgentID)
|
||||||
|
l.renderLinkUsersDone(w, r, authReq, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) renderLinkUsersDone(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) {
|
||||||
|
var errType, errMessage string
|
||||||
|
data := l.getUserData(r, authReq, "Linking Users Done", errType, errMessage)
|
||||||
|
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplLinkUsersDone], data, nil)
|
||||||
|
}
|
@ -2,6 +2,7 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@ -28,11 +29,14 @@ type Login struct {
|
|||||||
renderer *Renderer
|
renderer *Renderer
|
||||||
parser *form.Parser
|
parser *form.Parser
|
||||||
authRepo auth_repository.Repository
|
authRepo auth_repository.Repository
|
||||||
|
baseURL string
|
||||||
zitadelURL string
|
zitadelURL string
|
||||||
oidcAuthCallbackURL string
|
oidcAuthCallbackURL string
|
||||||
|
IDPConfigAesCrypto crypto.EncryptionAlgorithm
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
BaseURL string
|
||||||
OidcAuthCallbackURL string
|
OidcAuthCallbackURL string
|
||||||
ZitadelURL string
|
ZitadelURL string
|
||||||
LanguageCookieName string
|
LanguageCookieName string
|
||||||
@ -53,11 +57,17 @@ const (
|
|||||||
handlerPrefix = "/login"
|
handlerPrefix = "/login"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateLogin(config Config, authRepo *eventsourcing.EsRepository, localDevMode bool) (*Login, string) {
|
func CreateLogin(config Config, authRepo *eventsourcing.EsRepository, systemDefaults systemdefaults.SystemDefaults, localDevMode bool) (*Login, string) {
|
||||||
|
aesCrypto, err := crypto.NewAESCrypto(systemDefaults.IDPConfigVerificationKey)
|
||||||
|
if err != nil {
|
||||||
|
logging.Log("HANDL-s90ew").WithError(err).Debug("error create new aes crypto")
|
||||||
|
}
|
||||||
login := &Login{
|
login := &Login{
|
||||||
oidcAuthCallbackURL: config.OidcAuthCallbackURL,
|
oidcAuthCallbackURL: config.OidcAuthCallbackURL,
|
||||||
|
baseURL: config.BaseURL,
|
||||||
zitadelURL: config.ZitadelURL,
|
zitadelURL: config.ZitadelURL,
|
||||||
authRepo: authRepo,
|
authRepo: authRepo,
|
||||||
|
IDPConfigAesCrypto: aesCrypto,
|
||||||
}
|
}
|
||||||
prefix := ""
|
prefix := ""
|
||||||
if localDevMode {
|
if localDevMode {
|
||||||
|
@ -30,12 +30,12 @@ func (l *Login) handleLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *Login) handleLoginName(w http.ResponseWriter, r *http.Request) {
|
func (l *Login) handleLoginName(w http.ResponseWriter, r *http.Request) {
|
||||||
authSession, err := l.getAuthRequest(r)
|
authReq, err := l.getAuthRequest(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.renderError(w, r, authSession, err)
|
l.renderError(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.renderLogin(w, r, authSession, nil)
|
l.renderLogin(w, r, authReq, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Login) handleLoginNameCheck(w http.ResponseWriter, r *http.Request) {
|
func (l *Login) handleLoginNameCheck(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -46,6 +46,10 @@ func (l *Login) handleLoginNameCheck(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if data.Register {
|
if data.Register {
|
||||||
|
if authReq.LoginPolicy != nil && authReq.LoginPolicy.AllowExternalIDP && authReq.AllowedExternalIDPs != nil && len(authReq.AllowedExternalIDPs) > 0 {
|
||||||
|
l.handleRegisterOption(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
l.handleRegister(w, r)
|
l.handleRegister(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
org_model "github.com/caos/zitadel/internal/org/model"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (l *Login) getOrgIamPolicy(r *http.Request, orgID string) (*org_model.OrgIAMPolicy, error) {
|
|
||||||
return l.authRepo.GetOrgIamPolicy(r.Context(), orgID)
|
|
||||||
}
|
|
18
internal/ui/login/handler/policy_handler.go
Normal file
18
internal/ui/login/handler/policy_handler.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
|
org_model "github.com/caos/zitadel/internal/org/model"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l *Login) getOrgIamPolicy(r *http.Request, orgID string) (*org_model.OrgIAMPolicy, error) {
|
||||||
|
if orgID == "" {
|
||||||
|
return l.authRepo.GetDefaultOrgIamPolicy(r.Context()), nil
|
||||||
|
}
|
||||||
|
return l.authRepo.GetOrgIamPolicy(r.Context(), orgID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) getIDPConfigByID(r *http.Request, idpConfigID string) (*iam_model.IDPConfigView, error) {
|
||||||
|
return l.authRepo.GetIDPConfigByID(r.Context(), idpConfigID)
|
||||||
|
}
|
@ -66,11 +66,16 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resourceOwner := iam.GlobalOrgID
|
||||||
member := &org_model.OrgMember{
|
member := &org_model.OrgMember{
|
||||||
ObjectRoot: models.ObjectRoot{AggregateID: iam.GlobalOrgID},
|
ObjectRoot: models.ObjectRoot{AggregateID: iam.GlobalOrgID},
|
||||||
Roles: []string{orgProjectCreatorRole},
|
Roles: []string{orgProjectCreatorRole},
|
||||||
}
|
}
|
||||||
user, err := l.authRepo.Register(setContext(r.Context(), iam.GlobalOrgID), data.toUserModel(), member, iam.GlobalOrgID)
|
if authRequest.GetScopeOrgID() != "" && authRequest.GetScopeOrgID() != iam.GlobalOrgID {
|
||||||
|
member = nil
|
||||||
|
resourceOwner = authRequest.GetScopeOrgID()
|
||||||
|
}
|
||||||
|
user, err := l.authRepo.Register(setContext(r.Context(), resourceOwner), data.toUserModel(), member, resourceOwner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.renderRegister(w, r, authRequest, data, err)
|
l.renderRegister(w, r, authRequest, data, err)
|
||||||
return
|
return
|
||||||
|
53
internal/ui/login/handler/register_option_handler.go
Normal file
53
internal/ui/login/handler/register_option_handler.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/auth_request/model"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tmplRegisterOption = "registeroption"
|
||||||
|
)
|
||||||
|
|
||||||
|
type registerOptionFormData struct {
|
||||||
|
UsernamePassword bool `schema:"usernamepassword"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type registerOptionData struct {
|
||||||
|
baseData
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) handleRegisterOption(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := new(registerOptionFormData)
|
||||||
|
authRequest, err := l.getAuthRequestAndParseData(r, data)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.renderRegisterOption(w, r, authRequest, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) renderRegisterOption(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) {
|
||||||
|
var errType, errMessage string
|
||||||
|
if err != nil {
|
||||||
|
errMessage = l.getErrorMessage(r, err)
|
||||||
|
}
|
||||||
|
data := registerOptionData{
|
||||||
|
baseData: l.getBaseData(r, authReq, "RegisterOption", errType, errMessage),
|
||||||
|
}
|
||||||
|
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplRegisterOption], data, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) handleRegisterOptionCheck(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := new(registerOptionFormData)
|
||||||
|
authReq, err := l.getAuthRequestAndParseData(r, data)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if data.UsernamePassword {
|
||||||
|
l.handleRegister(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.handleRegisterOption(w, r)
|
||||||
|
}
|
@ -3,14 +3,14 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/caos/logging"
|
||||||
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
|
"golang.org/x/text/language"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
|
||||||
"github.com/gorilla/csrf"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
|
|
||||||
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
|
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
|
||||||
"github.com/caos/zitadel/internal/auth_request/model"
|
"github.com/caos/zitadel/internal/auth_request/model"
|
||||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
@ -32,28 +32,31 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, cookieName str
|
|||||||
pathPrefix: pathPrefix,
|
pathPrefix: pathPrefix,
|
||||||
}
|
}
|
||||||
tmplMapping := map[string]string{
|
tmplMapping := map[string]string{
|
||||||
tmplError: "error.html",
|
tmplError: "error.html",
|
||||||
tmplLogin: "login.html",
|
tmplLogin: "login.html",
|
||||||
tmplUserSelection: "select_user.html",
|
tmplUserSelection: "select_user.html",
|
||||||
tmplPassword: "password.html",
|
tmplPassword: "password.html",
|
||||||
tmplMfaVerify: "mfa_verify.html",
|
tmplMfaVerify: "mfa_verify.html",
|
||||||
tmplMfaPrompt: "mfa_prompt.html",
|
tmplMfaPrompt: "mfa_prompt.html",
|
||||||
tmplMfaInitVerify: "mfa_init_verify.html",
|
tmplMfaInitVerify: "mfa_init_verify.html",
|
||||||
tmplMfaInitDone: "mfa_init_done.html",
|
tmplMfaInitDone: "mfa_init_done.html",
|
||||||
tmplMailVerification: "mail_verification.html",
|
tmplMailVerification: "mail_verification.html",
|
||||||
tmplMailVerified: "mail_verified.html",
|
tmplMailVerified: "mail_verified.html",
|
||||||
tmplInitPassword: "init_password.html",
|
tmplInitPassword: "init_password.html",
|
||||||
tmplInitPasswordDone: "init_password_done.html",
|
tmplInitPasswordDone: "init_password_done.html",
|
||||||
tmplInitUser: "init_user.html",
|
tmplInitUser: "init_user.html",
|
||||||
tmplInitUserDone: "init_user_done.html",
|
tmplInitUserDone: "init_user_done.html",
|
||||||
tmplPasswordResetDone: "password_reset_done.html",
|
tmplPasswordResetDone: "password_reset_done.html",
|
||||||
tmplChangePassword: "change_password.html",
|
tmplChangePassword: "change_password.html",
|
||||||
tmplChangePasswordDone: "change_password_done.html",
|
tmplChangePasswordDone: "change_password_done.html",
|
||||||
tmplRegister: "register.html",
|
tmplRegisterOption: "register_option.html",
|
||||||
tmplLogoutDone: "logout_done.html",
|
tmplRegister: "register.html",
|
||||||
tmplRegisterOrg: "register_org.html",
|
tmplLogoutDone: "logout_done.html",
|
||||||
tmplChangeUsername: "change_username.html",
|
tmplRegisterOrg: "register_org.html",
|
||||||
tmplChangeUsernameDone: "change_username_done.html",
|
tmplChangeUsername: "change_username.html",
|
||||||
|
tmplChangeUsernameDone: "change_username_done.html",
|
||||||
|
tmplLinkUsersDone: "link_users_done.html",
|
||||||
|
tmplExternalNotFoundOption: "external_not_found_option.html",
|
||||||
}
|
}
|
||||||
funcs := map[string]interface{}{
|
funcs := map[string]interface{}{
|
||||||
"resourceUrl": func(file string) string {
|
"resourceUrl": func(file string) string {
|
||||||
@ -65,6 +68,12 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, cookieName str
|
|||||||
"loginUrl": func() string {
|
"loginUrl": func() string {
|
||||||
return path.Join(r.pathPrefix, EndpointLogin)
|
return path.Join(r.pathPrefix, EndpointLogin)
|
||||||
},
|
},
|
||||||
|
"externalIDPAuthURL": func(authReqID, idpConfigID string) string {
|
||||||
|
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s&%s=%s", EndpointExternalLogin, queryAuthRequestID, authReqID, queryIDPConfigID, idpConfigID))
|
||||||
|
},
|
||||||
|
"externalIDPRegisterURL": func(authReqID, idpConfigID string) string {
|
||||||
|
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s&%s=%s", EndpointExternalRegister, queryAuthRequestID, authReqID, queryIDPConfigID, idpConfigID))
|
||||||
|
},
|
||||||
"registerUrl": func(id string) string {
|
"registerUrl": func(id string) string {
|
||||||
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s", EndpointRegister, queryAuthRequestID, id))
|
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s", EndpointRegister, queryAuthRequestID, id))
|
||||||
},
|
},
|
||||||
@ -107,6 +116,9 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, cookieName str
|
|||||||
"changePasswordUrl": func() string {
|
"changePasswordUrl": func() string {
|
||||||
return path.Join(r.pathPrefix, EndpointChangePassword)
|
return path.Join(r.pathPrefix, EndpointChangePassword)
|
||||||
},
|
},
|
||||||
|
"registerOptionUrl": func() string {
|
||||||
|
return path.Join(r.pathPrefix, EndpointRegisterOption)
|
||||||
|
},
|
||||||
"registrationUrl": func() string {
|
"registrationUrl": func() string {
|
||||||
return path.Join(r.pathPrefix, EndpointRegister)
|
return path.Join(r.pathPrefix, EndpointRegister)
|
||||||
},
|
},
|
||||||
@ -116,6 +128,9 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, cookieName str
|
|||||||
"changeUsernameUrl": func() string {
|
"changeUsernameUrl": func() string {
|
||||||
return path.Join(r.pathPrefix, EndpointChangeUsername)
|
return path.Join(r.pathPrefix, EndpointChangeUsername)
|
||||||
},
|
},
|
||||||
|
"externalNotFoundOptionUrl": func() string {
|
||||||
|
return path.Join(r.pathPrefix, EndpointExternalNotFoundOption)
|
||||||
|
},
|
||||||
"selectedLanguage": func(l string) bool {
|
"selectedLanguage": func(l string) bool {
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
@ -186,6 +201,10 @@ func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq *
|
|||||||
l.renderInitUser(w, r, authReq, "", "", step.PasswordSet, nil)
|
l.renderInitUser(w, r, authReq, "", "", step.PasswordSet, nil)
|
||||||
case *model.ChangeUsernameStep:
|
case *model.ChangeUsernameStep:
|
||||||
l.renderChangeUsername(w, r, authReq, nil)
|
l.renderChangeUsername(w, r, authReq, nil)
|
||||||
|
case *model.LinkUsersStep:
|
||||||
|
l.linkUsers(w, r, authReq, err)
|
||||||
|
case *model.ExternalNotFoundOptionStep:
|
||||||
|
l.renderExternalNotFoundOption(w, r, authReq, err)
|
||||||
default:
|
default:
|
||||||
l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "APP-ds3QF", "step no possible"))
|
l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "APP-ds3QF", "step no possible"))
|
||||||
}
|
}
|
||||||
@ -204,11 +223,12 @@ func (l *Login) getUserData(r *http.Request, authReq *model.AuthRequest, title s
|
|||||||
return userData{
|
return userData{
|
||||||
baseData: l.getBaseData(r, authReq, title, errType, errMessage),
|
baseData: l.getBaseData(r, authReq, title, errType, errMessage),
|
||||||
profileData: l.getProfileData(authReq),
|
profileData: l.getProfileData(authReq),
|
||||||
|
Linking: len(authReq.LinkingUsers) > 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Login) getBaseData(r *http.Request, authReq *model.AuthRequest, title string, errType, errMessage string) baseData {
|
func (l *Login) getBaseData(r *http.Request, authReq *model.AuthRequest, title string, errType, errMessage string) baseData {
|
||||||
return baseData{
|
baseData := baseData{
|
||||||
errorData: errorData{
|
errorData: errorData{
|
||||||
ErrType: errType,
|
ErrType: errType,
|
||||||
ErrMessage: errMessage,
|
ErrMessage: errMessage,
|
||||||
@ -217,10 +237,16 @@ func (l *Login) getBaseData(r *http.Request, authReq *model.AuthRequest, title s
|
|||||||
Title: title,
|
Title: title,
|
||||||
Theme: l.getTheme(r),
|
Theme: l.getTheme(r),
|
||||||
ThemeMode: l.getThemeMode(r),
|
ThemeMode: l.getThemeMode(r),
|
||||||
|
OrgID: l.getOrgID(authReq),
|
||||||
AuthReqID: getRequestID(authReq, r),
|
AuthReqID: getRequestID(authReq, r),
|
||||||
CSRF: csrf.TemplateField(r),
|
CSRF: csrf.TemplateField(r),
|
||||||
Nonce: http_mw.GetNonce(r),
|
Nonce: http_mw.GetNonce(r),
|
||||||
}
|
}
|
||||||
|
if authReq != nil {
|
||||||
|
baseData.LoginPolicy = authReq.LoginPolicy
|
||||||
|
baseData.IDPProviders = authReq.AllowedExternalIDPs
|
||||||
|
}
|
||||||
|
return baseData
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Login) getProfileData(authReq *model.AuthRequest) profileData {
|
func (l *Login) getProfileData(authReq *model.AuthRequest) profileData {
|
||||||
@ -253,6 +279,19 @@ func (l *Login) getThemeMode(r *http.Request) string {
|
|||||||
return "" //TODO: impl
|
return "" //TODO: impl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Login) getOrgID(authReq *model.AuthRequest) string {
|
||||||
|
if authReq == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if authReq.UserOrgID != "" {
|
||||||
|
return authReq.UserOrgID
|
||||||
|
}
|
||||||
|
if authReq.Request == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return authReq.GetScopeOrgID()
|
||||||
|
}
|
||||||
|
|
||||||
func getRequestID(authReq *model.AuthRequest, r *http.Request) string {
|
func getRequestID(authReq *model.AuthRequest, r *http.Request) string {
|
||||||
if authReq != nil {
|
if authReq != nil {
|
||||||
return authReq.ID
|
return authReq.ID
|
||||||
@ -275,13 +314,16 @@ func (l *Login) cspErrorHandler(err error) http.Handler {
|
|||||||
|
|
||||||
type baseData struct {
|
type baseData struct {
|
||||||
errorData
|
errorData
|
||||||
Lang string
|
Lang string
|
||||||
Title string
|
Title string
|
||||||
Theme string
|
Theme string
|
||||||
ThemeMode string
|
ThemeMode string
|
||||||
AuthReqID string
|
OrgID string
|
||||||
CSRF template.HTML
|
AuthReqID string
|
||||||
Nonce string
|
CSRF template.HTML
|
||||||
|
Nonce string
|
||||||
|
LoginPolicy *iam_model.LoginPolicyView
|
||||||
|
IDPProviders []*iam_model.IDPProviderView
|
||||||
}
|
}
|
||||||
|
|
||||||
type errorData struct {
|
type errorData struct {
|
||||||
@ -295,6 +337,7 @@ type userData struct {
|
|||||||
PasswordChecked string
|
PasswordChecked string
|
||||||
MfaProviders []model.MfaType
|
MfaProviders []model.MfaType
|
||||||
SelectedMfaProvider model.MfaType
|
SelectedMfaProvider model.MfaType
|
||||||
|
Linking bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type profileData struct {
|
type profileData struct {
|
||||||
@ -315,7 +358,8 @@ type passwordData struct {
|
|||||||
|
|
||||||
type userSelectionData struct {
|
type userSelectionData struct {
|
||||||
baseData
|
baseData
|
||||||
Users []model.UserSelection
|
Users []model.UserSelection
|
||||||
|
Linking bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type mfaData struct {
|
type mfaData struct {
|
||||||
|
@ -7,26 +7,32 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EndpointRoot = "/"
|
EndpointRoot = "/"
|
||||||
EndpointHealthz = "/healthz"
|
EndpointHealthz = "/healthz"
|
||||||
EndpointReadiness = "/ready"
|
EndpointReadiness = "/ready"
|
||||||
EndpointLogin = "/login"
|
EndpointLogin = "/login"
|
||||||
EndpointLoginName = "/loginname"
|
EndpointExternalLogin = "/login/externalidp"
|
||||||
EndpointUserSelection = "/userselection"
|
EndpointExternalLoginCallback = "/login/externalidp/callback"
|
||||||
EndpointChangeUsername = "/username/change"
|
EndpointLoginName = "/loginname"
|
||||||
EndpointPassword = "/password"
|
EndpointUserSelection = "/userselection"
|
||||||
EndpointInitPassword = "/password/init"
|
EndpointChangeUsername = "/username/change"
|
||||||
EndpointChangePassword = "/password/change"
|
EndpointPassword = "/password"
|
||||||
EndpointPasswordReset = "/password/reset"
|
EndpointInitPassword = "/password/init"
|
||||||
EndpointInitUser = "/user/init"
|
EndpointChangePassword = "/password/change"
|
||||||
EndpointMfaVerify = "/mfa/verify"
|
EndpointPasswordReset = "/password/reset"
|
||||||
EndpointMfaPrompt = "/mfa/prompt"
|
EndpointInitUser = "/user/init"
|
||||||
EndpointMfaInitVerify = "/mfa/init/verify"
|
EndpointMfaVerify = "/mfa/verify"
|
||||||
EndpointMailVerification = "/mail/verification"
|
EndpointMfaPrompt = "/mfa/prompt"
|
||||||
EndpointMailVerified = "/mail/verified"
|
EndpointMfaInitVerify = "/mfa/init/verify"
|
||||||
EndpointRegister = "/register"
|
EndpointMailVerification = "/mail/verification"
|
||||||
EndpointRegisterOrg = "/register/org"
|
EndpointMailVerified = "/mail/verified"
|
||||||
EndpointLogoutDone = "/logout/done"
|
EndpointRegisterOption = "/register/option"
|
||||||
|
EndpointRegister = "/register"
|
||||||
|
EndpointExternalRegister = "/register/externalidp"
|
||||||
|
EndpointExternalRegisterCallback = "/register/externalidp/callback"
|
||||||
|
EndpointRegisterOrg = "/register/org"
|
||||||
|
EndpointLogoutDone = "/logout/done"
|
||||||
|
EndpointExternalNotFoundOption = "/externaluser/option"
|
||||||
|
|
||||||
EndpointResources = "/resources"
|
EndpointResources = "/resources"
|
||||||
)
|
)
|
||||||
@ -38,6 +44,8 @@ func CreateRouter(login *Login, staticDir http.FileSystem, interceptors ...mux.M
|
|||||||
router.HandleFunc(EndpointHealthz, login.handleHealthz).Methods(http.MethodGet)
|
router.HandleFunc(EndpointHealthz, login.handleHealthz).Methods(http.MethodGet)
|
||||||
router.HandleFunc(EndpointReadiness, login.handleReadiness).Methods(http.MethodGet)
|
router.HandleFunc(EndpointReadiness, login.handleReadiness).Methods(http.MethodGet)
|
||||||
router.HandleFunc(EndpointLogin, login.handleLogin).Methods(http.MethodGet, http.MethodPost)
|
router.HandleFunc(EndpointLogin, login.handleLogin).Methods(http.MethodGet, http.MethodPost)
|
||||||
|
router.HandleFunc(EndpointExternalLogin, login.handleExternalLogin).Methods(http.MethodGet)
|
||||||
|
router.HandleFunc(EndpointExternalLoginCallback, login.handleExternalLoginCallback).Methods(http.MethodGet)
|
||||||
router.HandleFunc(EndpointLoginName, login.handleLoginName).Methods(http.MethodGet)
|
router.HandleFunc(EndpointLoginName, login.handleLoginName).Methods(http.MethodGet)
|
||||||
router.HandleFunc(EndpointLoginName, login.handleLoginNameCheck).Methods(http.MethodPost)
|
router.HandleFunc(EndpointLoginName, login.handleLoginNameCheck).Methods(http.MethodPost)
|
||||||
router.HandleFunc(EndpointUserSelection, login.handleSelectUser).Methods(http.MethodPost)
|
router.HandleFunc(EndpointUserSelection, login.handleSelectUser).Methods(http.MethodPost)
|
||||||
@ -55,8 +63,13 @@ func CreateRouter(login *Login, staticDir http.FileSystem, interceptors ...mux.M
|
|||||||
router.HandleFunc(EndpointMailVerification, login.handleMailVerification).Methods(http.MethodGet)
|
router.HandleFunc(EndpointMailVerification, login.handleMailVerification).Methods(http.MethodGet)
|
||||||
router.HandleFunc(EndpointMailVerification, login.handleMailVerificationCheck).Methods(http.MethodPost)
|
router.HandleFunc(EndpointMailVerification, login.handleMailVerificationCheck).Methods(http.MethodPost)
|
||||||
router.HandleFunc(EndpointChangePassword, login.handleChangePassword).Methods(http.MethodPost)
|
router.HandleFunc(EndpointChangePassword, login.handleChangePassword).Methods(http.MethodPost)
|
||||||
|
router.HandleFunc(EndpointRegisterOption, login.handleRegisterOption).Methods(http.MethodGet)
|
||||||
|
router.HandleFunc(EndpointRegisterOption, login.handleRegisterOptionCheck).Methods(http.MethodPost)
|
||||||
|
router.HandleFunc(EndpointExternalNotFoundOption, login.handleExternalNotFoundOptionCheck).Methods(http.MethodPost)
|
||||||
router.HandleFunc(EndpointRegister, login.handleRegister).Methods(http.MethodGet)
|
router.HandleFunc(EndpointRegister, login.handleRegister).Methods(http.MethodGet)
|
||||||
router.HandleFunc(EndpointRegister, login.handleRegisterCheck).Methods(http.MethodPost)
|
router.HandleFunc(EndpointRegister, login.handleRegisterCheck).Methods(http.MethodPost)
|
||||||
|
router.HandleFunc(EndpointExternalRegister, login.handleExternalRegister).Methods(http.MethodGet)
|
||||||
|
router.HandleFunc(EndpointExternalRegisterCallback, login.handleExternalRegisterCallback).Methods(http.MethodGet)
|
||||||
router.HandleFunc(EndpointLogoutDone, login.handleLogoutDone).Methods(http.MethodGet)
|
router.HandleFunc(EndpointLogoutDone, login.handleLogoutDone).Methods(http.MethodGet)
|
||||||
router.PathPrefix(EndpointResources).Handler(login.handleResources(staticDir)).Methods(http.MethodGet)
|
router.PathPrefix(EndpointResources).Handler(login.handleResources(staticDir)).Methods(http.MethodGet)
|
||||||
router.HandleFunc(EndpointRegisterOrg, login.handleRegisterOrg).Methods(http.MethodGet)
|
router.HandleFunc(EndpointRegisterOrg, login.handleRegisterOrg).Methods(http.MethodGet)
|
||||||
|
@ -19,6 +19,7 @@ func (l *Login) renderUserSelection(w http.ResponseWriter, r *http.Request, auth
|
|||||||
data := userSelectionData{
|
data := userSelectionData{
|
||||||
baseData: l.getBaseData(r, authReq, "Select User", "", ""),
|
baseData: l.getBaseData(r, authReq, "Select User", "", ""),
|
||||||
Users: selectionData.Users,
|
Users: selectionData.Users,
|
||||||
|
Linking: len(authReq.LinkingUsers) > 0,
|
||||||
}
|
}
|
||||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplUserSelection], data, nil)
|
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplUserSelection], data, nil)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package login
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing"
|
"github.com/caos/zitadel/internal/auth/repository/eventsourcing"
|
||||||
|
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
"github.com/caos/zitadel/internal/ui/login/handler"
|
"github.com/caos/zitadel/internal/ui/login/handler"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -9,6 +10,6 @@ type Config struct {
|
|||||||
Handler handler.Config
|
Handler handler.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func Start(config Config, authRepo *eventsourcing.EsRepository, localDevMode bool) (*handler.Login, string) {
|
func Start(config Config, authRepo *eventsourcing.EsRepository, systemdefaults systemdefaults.SystemDefaults, localDevMode bool) (*handler.Login, string) {
|
||||||
return handler.CreateLogin(config.Handler, authRepo, localDevMode)
|
return handler.CreateLogin(config.Handler, authRepo, systemdefaults, localDevMode)
|
||||||
}
|
}
|
||||||
|
@ -11,12 +11,17 @@ Password:
|
|||||||
Login:
|
Login:
|
||||||
Title: Anmeldung
|
Title: Anmeldung
|
||||||
Description: Gib deine Benutzerdaten ein.
|
Description: Gib deine Benutzerdaten ein.
|
||||||
|
TitleLinking: Anmeldung für Benutzer Linking
|
||||||
|
DescriptionLinking: Gib deine Benutzerdaten ein um den externen Benutzer mit einem ZITADEL Benutzer zu linken.
|
||||||
Loginname: Loginname
|
Loginname: Loginname
|
||||||
LoginnamePlaceHolder: username@domain
|
LoginnamePlaceHolder: username@domain
|
||||||
|
ExternalLogin: Melde dich mit einem externen Benutzer an
|
||||||
|
|
||||||
UserSelection:
|
UserSelection:
|
||||||
Title: Account auswählen
|
Title: Account auswählen
|
||||||
Description: Wähle deinen Account aus.
|
Description: Wähle deinen Account aus.
|
||||||
|
TitleLinking: Account auswählen um zu verlinken
|
||||||
|
DescriptionLinking: Wähle deinen Account, um diesen mit deinem externen Benutzer zu verlinken.
|
||||||
OtherUser: Anderer Benutzer
|
OtherUser: Anderer Benutzer
|
||||||
SessionState0: aktiv
|
SessionState0: aktiv
|
||||||
SessionState1: inaktiv
|
SessionState1: inaktiv
|
||||||
@ -103,6 +108,11 @@ EmailVerificationDone:
|
|||||||
Title: E-Mail Verifizierung
|
Title: E-Mail Verifizierung
|
||||||
Description: Deine E-Mail Adresse wurde erfolgreich verifiziert.
|
Description: Deine E-Mail Adresse wurde erfolgreich verifiziert.
|
||||||
|
|
||||||
|
RegisterOption:
|
||||||
|
Title: Registrations Möglichkeiten
|
||||||
|
Description: Wähle aus wie du dich registrieren möchtest.
|
||||||
|
RegisterUsernamePassword: Mit Benutzername Passwort
|
||||||
|
|
||||||
Registration:
|
Registration:
|
||||||
Title: Registration
|
Title: Registration
|
||||||
Description: Gib deine Benutzerangaben an. Die E-Mail Adresse wird als Benutzernamen verwendet.
|
Description: Gib deine Benutzerangaben an. Die E-Mail Adresse wird als Benutzernamen verwendet.
|
||||||
@ -147,6 +157,16 @@ RegistrationOrg:
|
|||||||
TosLinkText: AGBs
|
TosLinkText: AGBs
|
||||||
TosLink: https://zitadel.ch/pdf/agb.pdf
|
TosLink: https://zitadel.ch/pdf/agb.pdf
|
||||||
|
|
||||||
|
LinkingUsersDone:
|
||||||
|
Title: Benutzerlinking
|
||||||
|
Description: Benuzterlinking erledigt.
|
||||||
|
|
||||||
|
ExternalNotFoundOption:
|
||||||
|
Title: Externer Benutzer
|
||||||
|
Description: Externer Benutzer konnte nicht gefunden werden. Willst du deinen Benutzer mit einem bestehenden verlinken oder diesen als neuen Benutzer registrieren.
|
||||||
|
Link: Verlinken
|
||||||
|
AutoRegister: Automatisches registrieren
|
||||||
|
|
||||||
LogoutDone:
|
LogoutDone:
|
||||||
Title: Ausgeloggt
|
Title: Ausgeloggt
|
||||||
Description: Du wurdest erfolgreich ausgeloggt.
|
Description: Du wurdest erfolgreich ausgeloggt.
|
||||||
@ -173,6 +193,7 @@ Errors:
|
|||||||
UserIDMissing: UserID ist leer
|
UserIDMissing: UserID ist leer
|
||||||
Invalid: Userdaten sind ungültig
|
Invalid: Userdaten sind ungültig
|
||||||
DomainNotAllowedAsUsername: Domäne ist bereits reserviert und kann nicht verwendet werden
|
DomainNotAllowedAsUsername: Domäne ist bereits reserviert und kann nicht verwendet werden
|
||||||
|
NotAllowedToLink: Der Benutzer darf nicht mit einem externen Login Provider verlinkt werden
|
||||||
Password:
|
Password:
|
||||||
ConfirmationWrong: Passwort Bestätigung stimmt nicht überein
|
ConfirmationWrong: Passwort Bestätigung stimmt nicht überein
|
||||||
Empty: Passwort ist leer
|
Empty: Passwort ist leer
|
||||||
@ -202,5 +223,7 @@ Errors:
|
|||||||
NotReady: Multifaktor OTP (OneTimePassword) ist nicht bereit
|
NotReady: Multifaktor OTP (OneTimePassword) ist nicht bereit
|
||||||
Locked: Benutzer ist gesperrt
|
Locked: Benutzer ist gesperrt
|
||||||
NotActive: Benutzer ist nicht aktiv
|
NotActive: Benutzer ist nicht aktiv
|
||||||
|
ExternalIDP:
|
||||||
|
IDPTypeNotImplemented: IDP Typ ist nicht implementiert
|
||||||
|
|
||||||
optional: (optional)
|
optional: (optional)
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
Login:
|
Login:
|
||||||
Title: Login
|
Title: Login
|
||||||
Description: Enter your logindata.
|
Description: Enter your logindata.
|
||||||
|
TitleLinking: Login for userlinking
|
||||||
|
DescriptionLinking: Enter your login data to link your external user with a ZITADEL user.
|
||||||
Loginname: Loginname
|
Loginname: Loginname
|
||||||
LoginnamePlaceHolder: username@domain
|
LoginnamePlaceHolder: username@domain
|
||||||
|
ExternalLogin: Login with an external user.
|
||||||
|
|
||||||
UserSelection:
|
UserSelection:
|
||||||
Title: Select account
|
Title: Select account
|
||||||
Description: Select your account.
|
Description: Select your account.
|
||||||
|
TitleLinking: Select account for userlinking
|
||||||
|
DescriptionLinking: Select your account to link with your external user.
|
||||||
OtherUser: Other User
|
OtherUser: Other User
|
||||||
SessionState0: active
|
SessionState0: active
|
||||||
SessionState1: inactive
|
SessionState1: inactive
|
||||||
@ -103,6 +108,11 @@ EmailVerificationDone:
|
|||||||
Title: E-Mail Verification
|
Title: E-Mail Verification
|
||||||
Description: Your email address has been successfully verified.
|
Description: Your email address has been successfully verified.
|
||||||
|
|
||||||
|
RegistrationOption:
|
||||||
|
Title: Registration Options
|
||||||
|
Description: Choose how you'd like to register
|
||||||
|
RegisterUsernamePassword: With username password
|
||||||
|
|
||||||
Registration:
|
Registration:
|
||||||
Title: Registration
|
Title: Registration
|
||||||
Description: Enter your Userdata. Your email address will be used as loginname.
|
Description: Enter your Userdata. Your email address will be used as loginname.
|
||||||
@ -147,11 +157,20 @@ RegistrationOrg:
|
|||||||
TosLinkText: TOS
|
TosLinkText: TOS
|
||||||
TosLink: https://zitadel.ch/pdf/tos.pdf
|
TosLink: https://zitadel.ch/pdf/tos.pdf
|
||||||
|
|
||||||
|
|
||||||
LogoutDone:
|
LogoutDone:
|
||||||
Title: Logged out
|
Title: Logged out
|
||||||
Description: You have logged out successfully.
|
Description: You have logged out successfully.
|
||||||
|
|
||||||
|
LinkingUsersDone:
|
||||||
|
Title: Userlinking
|
||||||
|
Description: Userlinking done.
|
||||||
|
|
||||||
|
ExternalNotFoundOption:
|
||||||
|
Title: External User
|
||||||
|
Description: External user not found. Do you want to link your user or auto register a new one.
|
||||||
|
Link: Link
|
||||||
|
AutoRegister: Auto register
|
||||||
|
|
||||||
Actions:
|
Actions:
|
||||||
Login: login
|
Login: login
|
||||||
Next: next
|
Next: next
|
||||||
@ -163,7 +182,6 @@ Actions:
|
|||||||
Cancel: cancel
|
Cancel: cancel
|
||||||
Save: save
|
Save: save
|
||||||
|
|
||||||
|
|
||||||
Errors:
|
Errors:
|
||||||
Internal: An internal error occured
|
Internal: An internal error occured
|
||||||
AuthRequest:
|
AuthRequest:
|
||||||
@ -175,6 +193,7 @@ Errors:
|
|||||||
UserIDMissing: UserID is empty
|
UserIDMissing: UserID is empty
|
||||||
Invalid: Invalid userdata
|
Invalid: Invalid userdata
|
||||||
DomainNotAllowedAsUsername: Domain is already reserved and cannot be used
|
DomainNotAllowedAsUsername: Domain is already reserved and cannot be used
|
||||||
|
NotAllowedToLink: User is not allowed to link with external login provider
|
||||||
Password:
|
Password:
|
||||||
ConfirmationWrong: Passwordconfirmation is wrong
|
ConfirmationWrong: Passwordconfirmation is wrong
|
||||||
Empty: Password is empty
|
Empty: Password is empty
|
||||||
@ -204,6 +223,8 @@ Errors:
|
|||||||
NotReady: Multifactor OTP (OneTimePassword) isn't ready
|
NotReady: Multifactor OTP (OneTimePassword) isn't ready
|
||||||
Locked: User is locked
|
Locked: User is locked
|
||||||
NotActive: User is not active
|
NotActive: User is not active
|
||||||
|
ExternalIDP:
|
||||||
|
IDPTypeNotImplemented: IDP Type is not implemented
|
||||||
|
|
||||||
|
|
||||||
optional: (optional)
|
optional: (optional)
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
*, *::before, *::after {
|
*, *::before, *::after {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: Lato;
|
font-family: Lato;
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,6 +97,7 @@ h1 {
|
|||||||
font-family: Aileron;
|
font-family: Aileron;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@ -122,7 +123,13 @@ header .logo {
|
|||||||
margin: 30px;
|
margin: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.head {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content form {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -137,11 +144,14 @@ a {
|
|||||||
a:hover {
|
a:hover {
|
||||||
color: #f60075;
|
color: #f60075;
|
||||||
}
|
}
|
||||||
|
a.tos-link {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button, .button {
|
||||||
background-color: #282828;
|
background-color: #282828;
|
||||||
color: #760038;
|
color: #760038;
|
||||||
border: 2px solid #760038;
|
border: 1px solid #760038;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
@ -149,36 +159,39 @@ button {
|
|||||||
transition: all 0.3s ease 0s;
|
transition: all 0.3s ease 0s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 40px;
|
||||||
}
|
}
|
||||||
button:hover {
|
button:hover, .button:hover {
|
||||||
background-color: #f60075;
|
background-color: #f60075;
|
||||||
color: #282828;
|
color: #282828;
|
||||||
border: 2px solid #f60075;
|
border: 1px solid #f60075;
|
||||||
}
|
}
|
||||||
button.primary {
|
button.primary, .button.primary {
|
||||||
background-color: #760038;
|
background-color: #760038;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
button.primary:hover {
|
button.primary:hover, .button.primary:hover {
|
||||||
background-color: #f60075;
|
background-color: #f60075;
|
||||||
}
|
}
|
||||||
button:disabled {
|
button:disabled, .button:disabled {
|
||||||
background-color: #505050;
|
background-color: #999999;
|
||||||
border: 2px solid #505050;
|
border: 1px solid #999999;
|
||||||
}
|
}
|
||||||
button:disabled:hover {
|
button:disabled:hover, .button:disabled:hover {
|
||||||
background-color: #505050;
|
background-color: #999999;
|
||||||
border: 2px solid #505050;
|
border: 1px solid #999999;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:not([type=radio]), select {
|
input:not([type=radio]), select {
|
||||||
background-color: #252525;
|
background-color: #252525;
|
||||||
color: white;
|
color: white;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
border: 2px solid #505050;
|
border: 1px solid #999999;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding-left: 15px;
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
form button.user-selection .profile-image, .login-profile .profile-image {
|
form button.user-selection .profile-image, .login-profile .profile-image {
|
||||||
@ -213,7 +226,7 @@ form button.user-selection:hover .profile-image, .login-profile:hover .profile-i
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.login-profile .names div:first-of-type {
|
.login-profile .names div:first-of-type {
|
||||||
font-size: 40px;
|
font-size: 26px;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
.login-profile .names div:nth-of-type(2) {
|
.login-profile .names div:nth-of-type(2) {
|
||||||
@ -244,20 +257,21 @@ form .field.check-box {
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
form .field.check-box input[type=checkbox] {
|
form .field.check-box input[type=checkbox] {
|
||||||
height: 20px;
|
height: 16px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
form .field.check-box label {
|
form .field.check-box label {
|
||||||
height: 20px;
|
height: 16px;
|
||||||
text-transform: inherit;
|
text-transform: inherit;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 2px 0 0 15px;
|
padding: 2px 0 0 15px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
form label {
|
form label {
|
||||||
color: #898989;
|
color: #898989;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 0.9rem;
|
font-size: 0.8rem;
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
}
|
}
|
||||||
form label span.optional {
|
form label span.optional {
|
||||||
@ -301,7 +315,7 @@ form button.user-selection .sessionstate {
|
|||||||
height: 20px;
|
height: 20px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
border-color: #505050;
|
border-color: #999999;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -362,6 +376,7 @@ form ul#passwordcomplexity {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
form ul#passwordcomplexity li {
|
form ul#passwordcomplexity li {
|
||||||
flex: 1 0 50%;
|
flex: 1 0 50%;
|
||||||
@ -444,4 +459,8 @@ footer {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #F20D6B;
|
||||||
|
}
|
||||||
|
|
||||||
/*# sourceMappingURL=dark.css.map */
|
/*# sourceMappingURL=dark.css.map */
|
||||||
|
@ -1 +1 @@
|
|||||||
{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/caos/variables.scss","../../scss/variables.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCMW;EDLX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCDc;EDEd,OCDQ;EDER;EACA;EACA;;;AAMJ;EACI,OCXQ;EDYR,aClBS;EDmBT;EACA,WEzBS;;;AF4Bb;EACI,OClBQ;EDmBR,aCzBS;ED0BT;EACA,WE/BU;;;AFkCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OChDW;EDiDX;EACA;;AAEA;EACI,OCpDY;;;ADwDpB;EACI,kBC5Dc;ED6Dd,OC3DW;ED4DX;EACA;EACA;EACA;EACA,QE7EU;EF8EV;EACA;EACA;;AACA;EACI,kBCpEY;EDqEZ,OCxEU;EDyEV;;AAGJ;EACI,kBC3EO;ED4EP,OC7EI;ED8EJ;;AACA;EACI,kBC9EQ;;ADkFhB;EACI,kBEzEW;EF0EX;;AAEA;EACI,kBE7EO;EF8EP;;;AAKZ;EACI,kBEnFmB;EFoFnB,OCjGQ;EDkGR,QE9GU;EF+GV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;EE9GN;;AACA;EFyGE;IExGA;IACA;;;AF+GA;EElHF;;AACA;EFiHE;IEhHA;IACA;;;;AFsHA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WE7IC;EF8ID;;AAGJ;EACI;EACA;EACA;EACA,OE/HC;;;AFqIT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;;AAIR;EACI,OE5KK;EF6KL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OCjNI;EDkNJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBE5MW;;AF+Mf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cErOO;EFsOP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OEjQP;;AFwQL;EACI;;AAEJ;EACI;EACA;EACA;EACA;EE3RV;;AACA;EFsRM;IErRJ;IACA;;;AF6RQ;EACI;EACA;EElSd;;AACA;EF+RU;IE9RR;IACA;;;AFoSI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OE3SN;;AFgTE;EACI,OElTL;;AFuTP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MC1VI;;AD6VR;EACI,MC/VU;;;ADoWd;EACI;EACA;;;AAIR;EAEQ;EAEJ;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA","file":"dark.css"}
|
{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/caos/variables.scss","../../scss/variables.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCMW;EDLX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCDc;EDEd,OCDQ;EDER;EACA;EACA;;;AAMJ;EACI,OCXQ;EDYR,aClBS;EDmBT;EACA,WEzBS;EF0BT;;;AAGJ;EACI,OCnBQ;EDoBR,aC1BS;ED2BT;EACA,WEhCU;;;AFmCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OCvDW;EDwDX;EACA;;AAEA;EACI,OC3DY;;AD8DhB;EACI;;;AAIR;EACI,kBCvEc;EDwEd,OCtEW;EDuEX;EACA;EACA;EACA;EACA,QExFU;EFyFV;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBCnFY;EDoFZ,OCvFU;EDwFV;;AAGJ;EACI,kBC1FO;ED2FP,OC5FI;ED6FJ;;AACA;EACI,kBC7FQ;;ADiGhB;EACI,kBEvFW;EFwFX;;AAEA;EACI,kBE3FO;EF4FP;;;AAOZ;EACI,kBEnGmB;EFoGnB,OClHQ;EDmHR,QE/HU;EFgIV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;EE9HN;;AACA;EFyHE;IExHA;IACA;;;AF+HA;EElIF;;AACA;EFiIE;IEhIA;IACA;;;;AFsIA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WE5JE;EF6JF;;AAGJ;EACI;EACA;EACA;EACA,OE/IC;;;AFqJT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OCpMA;;ADwMR;EACI,OE7LK;EF8LL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OCnOI;EDoOJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBE7NW;;AFgOf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cEtPO;EFuPP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OElRP;;AFyRL;EACI;;AAEJ;EACI;EACA;EACA;EACA;EE5SV;;AACA;EFuSM;IEtSJ;IACA;;;AF8SQ;EACI;EACA;EEnTd;;AACA;EFgTU;IE/SR;IACA;;;AFqTI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OE7TN;;AFkUE;EACI,OEpUL;;AFyUP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MC7WI;;ADgXR;EACI,MClXU;;;ADuXd;EACI;EACA;;;AAIR;EAEQ;EAEJ;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OEpZO","file":"dark.css"}
|
@ -73,7 +73,7 @@
|
|||||||
*, *::before, *::after {
|
*, *::before, *::after {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: Lato;
|
font-family: Lato;
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,6 +97,7 @@ h1 {
|
|||||||
font-family: Aileron;
|
font-family: Aileron;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@ -122,7 +123,13 @@ header .logo {
|
|||||||
margin: 30px;
|
margin: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.head {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content form {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -137,11 +144,14 @@ a {
|
|||||||
a:hover {
|
a:hover {
|
||||||
color: #f60075;
|
color: #f60075;
|
||||||
}
|
}
|
||||||
|
a.tos-link {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button, .button {
|
||||||
background-color: #282828;
|
background-color: #282828;
|
||||||
color: #760038;
|
color: #760038;
|
||||||
border: 2px solid #760038;
|
border: 1px solid #760038;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
@ -149,36 +159,39 @@ button {
|
|||||||
transition: all 0.3s ease 0s;
|
transition: all 0.3s ease 0s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 40px;
|
||||||
}
|
}
|
||||||
button:hover {
|
button:hover, .button:hover {
|
||||||
background-color: #f60075;
|
background-color: #f60075;
|
||||||
color: #282828;
|
color: #282828;
|
||||||
border: 2px solid #f60075;
|
border: 1px solid #f60075;
|
||||||
}
|
}
|
||||||
button.primary {
|
button.primary, .button.primary {
|
||||||
background-color: #760038;
|
background-color: #760038;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
button.primary:hover {
|
button.primary:hover, .button.primary:hover {
|
||||||
background-color: #f60075;
|
background-color: #f60075;
|
||||||
}
|
}
|
||||||
button:disabled {
|
button:disabled, .button:disabled {
|
||||||
background-color: #505050;
|
background-color: #999999;
|
||||||
border: 2px solid #505050;
|
border: 1px solid #999999;
|
||||||
}
|
}
|
||||||
button:disabled:hover {
|
button:disabled:hover, .button:disabled:hover {
|
||||||
background-color: #505050;
|
background-color: #999999;
|
||||||
border: 2px solid #505050;
|
border: 1px solid #999999;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:not([type=radio]), select {
|
input:not([type=radio]), select {
|
||||||
background-color: #252525;
|
background-color: #252525;
|
||||||
color: white;
|
color: white;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
border: 2px solid #505050;
|
border: 1px solid #999999;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding-left: 15px;
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
form button.user-selection .profile-image, .login-profile .profile-image {
|
form button.user-selection .profile-image, .login-profile .profile-image {
|
||||||
@ -213,7 +226,7 @@ form button.user-selection:hover .profile-image, .login-profile:hover .profile-i
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.login-profile .names div:first-of-type {
|
.login-profile .names div:first-of-type {
|
||||||
font-size: 40px;
|
font-size: 26px;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
.login-profile .names div:nth-of-type(2) {
|
.login-profile .names div:nth-of-type(2) {
|
||||||
@ -244,20 +257,21 @@ form .field.check-box {
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
form .field.check-box input[type=checkbox] {
|
form .field.check-box input[type=checkbox] {
|
||||||
height: 20px;
|
height: 16px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
form .field.check-box label {
|
form .field.check-box label {
|
||||||
height: 20px;
|
height: 16px;
|
||||||
text-transform: inherit;
|
text-transform: inherit;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 2px 0 0 15px;
|
padding: 2px 0 0 15px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
form label {
|
form label {
|
||||||
color: #898989;
|
color: #898989;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 0.9rem;
|
font-size: 0.8rem;
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
}
|
}
|
||||||
form label span.optional {
|
form label span.optional {
|
||||||
@ -301,7 +315,7 @@ form button.user-selection .sessionstate {
|
|||||||
height: 20px;
|
height: 20px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
border-color: #505050;
|
border-color: #999999;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -362,6 +376,7 @@ form ul#passwordcomplexity {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
form ul#passwordcomplexity li {
|
form ul#passwordcomplexity li {
|
||||||
flex: 1 0 50%;
|
flex: 1 0 50%;
|
||||||
@ -444,6 +459,10 @@ footer {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #F20D6B;
|
||||||
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
color: #282828;
|
color: #282828;
|
||||||
@ -454,66 +473,75 @@ html header .logo {
|
|||||||
html h1, html h2 {
|
html h1, html h2 {
|
||||||
color: #282828;
|
color: #282828;
|
||||||
}
|
}
|
||||||
html button {
|
html button, html .button {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
color: #760038;
|
color: #760038;
|
||||||
border: 2px solid #760038;
|
border: 1px solid #760038;
|
||||||
}
|
}
|
||||||
html button:hover {
|
html button:hover, html .button:hover {
|
||||||
background-color: #f60075;
|
background-color: #f60075;
|
||||||
border: 2px solid #f60075;
|
border: 1px solid #f60075;
|
||||||
|
color: #FFFFFF;
|
||||||
}
|
}
|
||||||
html button.primary {
|
html button.primary, html .button.primary {
|
||||||
background-color: #760038;
|
background-color: #760038;
|
||||||
color: white;
|
color: #FFFFFF;
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: 0px 10px 30px #760038;
|
box-shadow: 0px 10px 30px #760038;
|
||||||
}
|
}
|
||||||
html button.primary:hover {
|
html button.primary:hover, html .button.primary:hover {
|
||||||
background-color: #f60075;
|
background-color: #f60075;
|
||||||
}
|
}
|
||||||
html button.clean {
|
html button:disabled, html .button:disabled {
|
||||||
|
background-color: #999999;
|
||||||
|
border: 1px solid #999999;
|
||||||
|
}
|
||||||
|
html button:disabled:hover, html .button:disabled:hover {
|
||||||
|
background-color: #999999;
|
||||||
|
border: 1px solid #999999;
|
||||||
|
}
|
||||||
|
html button.clean, html .button.clean {
|
||||||
color: #282828;
|
color: #282828;
|
||||||
}
|
}
|
||||||
html button.clean:hover {
|
html button.clean:hover, html .button.clean:hover {
|
||||||
border: none;
|
border: none;
|
||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
}
|
}
|
||||||
html button.user-selection .profile-image {
|
html button.user-selection .profile-image, html .button.user-selection .profile-image {
|
||||||
background-image: url("../../../images/icon-user-light.png");
|
background-image: url("../../../images/icon-user-light.png");
|
||||||
}
|
}
|
||||||
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
||||||
html button.user-selection .profile-image {
|
html button.user-selection .profile-image, html .button.user-selection .profile-image {
|
||||||
background-image: url("../../../images/icon-user-light@2x.png");
|
background-image: url("../../../images/icon-user-light@2x.png");
|
||||||
background-size: 80px 80px;
|
background-size: 80px 80px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
html button.user-selection:hover {
|
html button.user-selection:hover, html .button.user-selection:hover {
|
||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
}
|
}
|
||||||
html button.user-selection:hover .profile-image {
|
html button.user-selection:hover .profile-image, html .button.user-selection:hover .profile-image {
|
||||||
background-image: url("../../../images/icon-user-light-hover.png");
|
background-image: url("../../../images/icon-user-light-hover.png");
|
||||||
}
|
}
|
||||||
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
||||||
html button.user-selection:hover .profile-image {
|
html button.user-selection:hover .profile-image, html .button.user-selection:hover .profile-image {
|
||||||
background-image: url("../../../images/icon-user-light-hover@2x.png");
|
background-image: url("../../../images/icon-user-light-hover@2x.png");
|
||||||
background-size: 80px 80px;
|
background-size: 80px 80px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
html button.other-user .other-user-image {
|
html button.other-user .other-user-image, html .button.other-user .other-user-image {
|
||||||
background-image: url("../../../images/icon-newuser-light.png");
|
background-image: url("../../../images/icon-newuser-light.png");
|
||||||
}
|
}
|
||||||
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
||||||
html button.other-user .other-user-image {
|
html button.other-user .other-user-image, html .button.other-user .other-user-image {
|
||||||
background-image: url("../../../images/icon-newuser-light@2x.png");
|
background-image: url("../../../images/icon-newuser-light@2x.png");
|
||||||
background-size: 80px 60px;
|
background-size: 80px 60px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
html button.other-user:hover .other-user-image {
|
html button.other-user:hover .other-user-image, html .button.other-user:hover .other-user-image {
|
||||||
background-image: url("../../../images/icon-newuser-light-hover.png");
|
background-image: url("../../../images/icon-newuser-light-hover.png");
|
||||||
}
|
}
|
||||||
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
||||||
html button.other-user:hover .other-user-image {
|
html button.other-user:hover .other-user-image, html .button.other-user:hover .other-user-image {
|
||||||
background-image: url("../../../images/icon-newuser-light-hover@2x.png");
|
background-image: url("../../../images/icon-newuser-light-hover@2x.png");
|
||||||
background-size: 80px 60px;
|
background-size: 80px 60px;
|
||||||
}
|
}
|
||||||
@ -532,4 +560,41 @@ html footer {
|
|||||||
background-image: url("../gradientdeco-full.svg");
|
background-image: url("../gradientdeco-full.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form .field.check-box label {
|
||||||
|
color: #282828;
|
||||||
|
}
|
||||||
|
form ul#passwordcomplexity li i {
|
||||||
|
color: #50CA3D;
|
||||||
|
}
|
||||||
|
form ul#passwordcomplexity li.invalid i {
|
||||||
|
color: #F20D6B;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-profile .profile-image, form button.user-selection .profile-image {
|
||||||
|
background-image: url("../../../images/icon-user-light.png");
|
||||||
|
}
|
||||||
|
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
||||||
|
.login-profile .profile-image, form button.user-selection .profile-image {
|
||||||
|
background-image: url("../../../images/icon-user-light@2x.png");
|
||||||
|
background-size: 80px 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.login-profile:hover .profile-image, form button.user-selection:hover .profile-image {
|
||||||
|
background-image: url("../../../images/icon-user-light-hover.png");
|
||||||
|
}
|
||||||
|
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
||||||
|
.login-profile:hover .profile-image, form button.user-selection:hover .profile-image {
|
||||||
|
background-image: url("../../../images/icon-user-light-hover@2x.png");
|
||||||
|
background-size: 80px 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.free-tier {
|
||||||
|
border: 2px solid #F20D6B;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #F20D6B;
|
||||||
|
}
|
||||||
|
|
||||||
/*# sourceMappingURL=light.css.map */
|
/*# sourceMappingURL=light.css.map */
|
||||||
|
@ -1 +1 @@
|
|||||||
{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/caos/variables.scss","../../scss/variables.scss","../../scss/light.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCMW;EDLX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCDc;EDEd,OCDQ;EDER;EACA;EACA;;;AAMJ;EACI,OCXQ;EDYR,aClBS;EDmBT;EACA,WEzBS;;;AF4Bb;EACI,OClBQ;EDmBR,aCzBS;ED0BT;EACA,WE/BU;;;AFkCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OChDW;EDiDX;EACA;;AAEA;EACI,OCpDY;;;ADwDpB;EACI,kBC5Dc;ED6Dd,OC3DW;ED4DX;EACA;EACA;EACA;EACA,QE7EU;EF8EV;EACA;EACA;;AACA;EACI,kBCpEY;EDqEZ,OCxEU;EDyEV;;AAGJ;EACI,kBC3EO;ED4EP,OC7EI;ED8EJ;;AACA;EACI,kBC9EQ;;ADkFhB;EACI,kBEzEW;EF0EX;;AAEA;EACI,kBE7EO;EF8EP;;;AAKZ;EACI,kBEnFmB;EFoFnB,OCjGQ;EDkGR,QE9GU;EF+GV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;EE9GN;;AACA;EFyGE;IExGA;IACA;;;AF+GA;EElHF;;AACA;EFiHE;IEhHA;IACA;;;;AFsHA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WE7IC;EF8ID;;AAGJ;EACI;EACA;EACA;EACA,OE/HC;;;AFqIT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;;AAIR;EACI,OE5KK;EF6KL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OCjNI;EDkNJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBE5MW;;AF+Mf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cErOO;EFsOP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OEjQP;;AFwQL;EACI;;AAEJ;EACI;EACA;EACA;EACA;EE3RV;;AACA;EFsRM;IErRJ;IACA;;;AF6RQ;EACI;EACA;EElSd;;AACA;EF+RU;IE9RR;IACA;;;AFoSI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OE3SN;;AFgTE;EACI,OElTL;;AFuTP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MC1VI;;AD6VR;EACI,MC/VU;;;ADoWd;EACI;EACA;;;AAIR;EAEQ;EAEJ;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AG/ZJ;EACI,kBFeQ;EEdR,OFac;;AERd;EACI;;AAGJ;EACI,OFGU;;AEAd;EACI;EACA;EACA;;AAEA;EACI,kBFIa;EEHb;;AAGJ;EACI,kBFTG;EEUH,OFXA;EEYA;EACA;;AACA;EACI,kBFbI;;AEiBZ;EACI,OFrBM;;AEuBN;EACI;EACA,kBDEY;;ACGhB;ED9BV;;AACA;EC6BU;ID5BR;IACA;;;AC+BQ;EACI,kBDRY;;ACUZ;EDrCd;;AACA;ECoCc;IDnCZ;IACA;;;ACyCQ;ED5CV;;AACA;EC2CU;ID1CR;IACA;;;AC8CY;EDjDd;;AACA;ECgDc;ID/CZ;IACA;;;ACqDA;EACI,kBD9BoB;EC+BpB,OF1DU;;AE8DV;EACI,MF/DM;;AEkEV;EACI,MFlEA;;AEsER;EAEQ","file":"light.css"}
|
{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/caos/variables.scss","../../scss/variables.scss","../../scss/light.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCMW;EDLX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCDc;EDEd,OCDQ;EDER;EACA;EACA;;;AAMJ;EACI,OCXQ;EDYR,aClBS;EDmBT;EACA,WEzBS;EF0BT;;;AAGJ;EACI,OCnBQ;EDoBR,aC1BS;ED2BT;EACA,WEhCU;;;AFmCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OCvDW;EDwDX;EACA;;AAEA;EACI,OC3DY;;AD8DhB;EACI;;;AAIR;EACI,kBCvEc;EDwEd,OCtEW;EDuEX;EACA;EACA;EACA;EACA,QExFU;EFyFV;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBCnFY;EDoFZ,OCvFU;EDwFV;;AAGJ;EACI,kBC1FO;ED2FP,OC5FI;ED6FJ;;AACA;EACI,kBC7FQ;;ADiGhB;EACI,kBEvFW;EFwFX;;AAEA;EACI,kBE3FO;EF4FP;;;AAOZ;EACI,kBEnGmB;EFoGnB,OClHQ;EDmHR,QE/HU;EFgIV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;EE9HN;;AACA;EFyHE;IExHA;IACA;;;AF+HA;EElIF;;AACA;EFiIE;IEhIA;IACA;;;;AFsIA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WE5JE;EF6JF;;AAGJ;EACI;EACA;EACA;EACA,OE/IC;;;AFqJT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OCpMA;;ADwMR;EACI,OE7LK;EF8LL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OCnOI;EDoOJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBE7NW;;AFgOf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cEtPO;EFuPP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OElRP;;AFyRL;EACI;;AAEJ;EACI;EACA;EACA;EACA;EE5SV;;AACA;EFuSM;IEtSJ;IACA;;;AF8SQ;EACI;EACA;EEnTd;;AACA;EFgTU;IE/SR;IACA;;;AFqTI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OE7TN;;AFkUE;EACI,OEpUL;;AFyUP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MC7WI;;ADgXR;EACI,MClXU;;;ADuXd;EACI;EACA;;;AAIR;EAEQ;EAEJ;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OEpZO;;;AClCX;EACI,kBFeQ;EEdR,OFac;;AERd;EACI;;AAGJ;EACI,OFGU;;AEAd;EACI;EACA;EACA;;AAEA;EACI,kBFIa;EEHb;EACA,ODqBgB;;AClBpB;EACI,kBFVG;EEWH,ODgBgB;ECfhB;EACA;;AACA;EACI,kBFdI;;AEkBZ;EACI,kBDRO;ECSP;;AAEA;EACI,kBDZG;ECaH;;AAIR;EACI,OFhCM;;AEkCN;EACI;EACA,kBDPY;;ACYhB;EDxCV;;AACA;ECuCU;IDtCR;IACA;;;ACyCQ;EACI,kBDjBY;;ACmBZ;ED/Cd;;AACA;EC8Cc;ID7CZ;IACA;;;ACmDQ;EDtDV;;AACA;ECqDU;IDpDR;IACA;;;ACwDY;ED3Dd;;AACA;EC0Dc;IDzDZ;IACA;;;AC+DA;EACI,kBDvCoB;ECwCpB,OFrEU;;AEyEV;EACI,MF1EM;;AE6EV;EACI,MF7EA;;AEiFR;EAEQ;;;AAMR;EACI,OF3FU;;AE+Fb;EACI,OD9DM;;ACkEN;EACI,ODpEG;;;AC4EZ;ED5GF;;AACA;EC2GE;ID1GA;IACA;;;AC6GA;EDhHF;;AACA;EC+GE;ID9GA;IACA;;;;ACkHJ;EACI;;;AAGJ;EACI,OD1FY","file":"light.css"}
|
@ -14,19 +14,20 @@ html {
|
|||||||
color: $fontColorLight;
|
color: $fontColorLight;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button, .button {
|
||||||
background-color: $backgroundColorLight;
|
background-color: $backgroundColorLight;
|
||||||
color: $primaryColorLight;
|
color: $primaryColorLight;
|
||||||
border: 2px solid $primaryColorLight;
|
border: 1px solid $primaryColorLight;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $primaryColorHoverLight;
|
background-color: $primaryColorHoverLight;
|
||||||
border: 2px solid $primaryColorHoverLight;
|
border: 1px solid $primaryColorHoverLight;
|
||||||
|
color: $buttonBackgroundColorHoverLight
|
||||||
}
|
}
|
||||||
|
|
||||||
&.primary {
|
&.primary {
|
||||||
background-color: $primaryColor;
|
background-color: $primaryColor;
|
||||||
color: $fontColor;
|
color: $buttonBackgroundColorHoverLight;
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: 0px 10px 30px $primaryColor;
|
box-shadow: 0px 10px 30px $primaryColor;
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -34,6 +35,16 @@ html {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background-color: $inputBorderColor;
|
||||||
|
border: 1px solid $inputBorderColor;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $inputBorderColor;
|
||||||
|
border: 1px solid $inputBorderColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.clean {
|
&.clean {
|
||||||
color: $fontColorLight;
|
color: $fontColorLight;
|
||||||
|
|
||||||
@ -90,4 +101,41 @@ html {
|
|||||||
background-image: url($footerimgLight);
|
background-image: url($footerimgLight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
.field.check-box label {
|
||||||
|
color: $fontColorLight;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul#passwordcomplexity li {
|
||||||
|
i {
|
||||||
|
color: $okColorLight;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.invalid {
|
||||||
|
i {
|
||||||
|
color: $nokColorLight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
%profile-image {
|
||||||
|
.profile-image {
|
||||||
|
@include retina-background-image($profileImgLight, "png", false, 80px, 80px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .profile-image {
|
||||||
|
@include retina-background-image($profileImgLight, "png", true, 80px, 80px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.free-tier {
|
||||||
|
border: 2px solid $nokColorLight;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: $nokColorLight;
|
||||||
}
|
}
|
@ -3,7 +3,7 @@
|
|||||||
*, *::before, *::after {
|
*, *::before, *::after {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: $standardFont;
|
font-family: $standardFont;
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,6 +30,7 @@ h1 {
|
|||||||
font-family: $headerFont;
|
font-family: $headerFont;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-size: $headerSize;
|
font-size: $headerSize;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@ -56,7 +57,13 @@ header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.head {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content form {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -71,12 +78,16 @@ a {
|
|||||||
&:hover {
|
&:hover {
|
||||||
color: $primaryColorHover;
|
color: $primaryColorHover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.tos-link {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button, .button {
|
||||||
background-color: $backgroundColor;
|
background-color: $backgroundColor;
|
||||||
color: $primaryColor;
|
color: $primaryColor;
|
||||||
border: 2px solid $primaryColor;
|
border: 1px solid $primaryColor;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
@ -84,10 +95,14 @@ button {
|
|||||||
transition: all 0.3s ease 0s;
|
transition: all 0.3s ease 0s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 40px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $primaryColorHover;
|
background-color: $primaryColorHover;
|
||||||
color: $backgroundColor;
|
color: $backgroundColor;
|
||||||
border: 2px solid $primaryColorHover;
|
border: 1px solid $primaryColorHover;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.primary {
|
&.primary {
|
||||||
@ -101,22 +116,24 @@ button {
|
|||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
background-color: $inputBorderColor;
|
background-color: $inputBorderColor;
|
||||||
border: 2px solid $inputBorderColor;
|
border: 1px solid $inputBorderColor;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $inputBorderColor;
|
background-color: $inputBorderColor;
|
||||||
border: 2px solid $inputBorderColor;
|
border: 1px solid $inputBorderColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
input:not([type='radio']), select {
|
input:not([type='radio']), select {
|
||||||
background-color: $inputBackgroundColor;
|
background-color: $inputBackgroundColor;
|
||||||
color: $fontColor;
|
color: $fontColor;
|
||||||
height: $inputHeight;
|
height: $inputHeight;
|
||||||
border: 2px solid $inputBorderColor;
|
border: 1px solid $inputBorderColor;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding-left: 15px;
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
%profile-image {
|
%profile-image {
|
||||||
@ -145,7 +162,7 @@ input:not([type='radio']), select {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
div:first-of-type {
|
div:first-of-type {
|
||||||
font-size: $headerSize;
|
font-size: $header3Size;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,23 +201,24 @@ form {
|
|||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
input[type='checkbox'] {
|
input[type='checkbox'] {
|
||||||
height: 20px;
|
height: 16px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
& label {
|
& label {
|
||||||
height: 20px;
|
height: 16px;
|
||||||
text-transform: inherit;
|
text-transform: inherit;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 2px 0 0 15px;
|
padding: 2px 0 0 15px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
color: $fontColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
color: $labelColor;
|
color: $labelColor;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 0.9rem;
|
font-size: 0.8rem;
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
|
|
||||||
span.optional {
|
span.optional {
|
||||||
@ -320,6 +338,7 @@ form {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
flex: 1 0 50%;
|
flex: 1 0 50%;
|
||||||
@ -412,7 +431,11 @@ footer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.free-tier {
|
.free-tier {
|
||||||
border: 2px solid #F20D6B;
|
border: 2px solid $nokColor;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: $nokColor;
|
||||||
}
|
}
|
@ -6,6 +6,7 @@ $headerFont: Lato;
|
|||||||
$inputHeight: 50px;
|
$inputHeight: 50px;
|
||||||
$headerSize: 40px;
|
$headerSize: 40px;
|
||||||
$header2Size: 30px;
|
$header2Size: 30px;
|
||||||
|
$header3Size: 26px;
|
||||||
$retina: "only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx)";
|
$retina: "only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx)";
|
||||||
|
|
||||||
@mixin retina-background-image($file, $type, $hover, $width, $height) {
|
@mixin retina-background-image($file, $type, $hover, $width, $height) {
|
||||||
@ -27,13 +28,14 @@ $fontColor: #BBBBC8;
|
|||||||
$primaryColor: #3574C6;
|
$primaryColor: #3574C6;
|
||||||
$primaryColorHover: lighten($primaryColor, 10%);
|
$primaryColorHover: lighten($primaryColor, 10%);
|
||||||
$labelColor: #898989;
|
$labelColor: #898989;
|
||||||
$inputBorderColor: #505050;
|
$inputBorderColor: #999999;
|
||||||
$inputBackgroundColor: #252525;
|
$inputBackgroundColor: #252525;
|
||||||
$buttonBackgroundColorHover: $inputBackgroundColor;
|
$buttonBackgroundColorHover: $inputBackgroundColor;
|
||||||
$profileImgDark: "../../../images/icon-user-dark";
|
$profileImgDark: "../../../images/icon-user-dark";
|
||||||
$otherUserImgDark: "../../../images/icon-newuser-dark";
|
$otherUserImgDark: "../../../images/icon-newuser-dark";
|
||||||
$nokColor: #F20D6B;
|
$nokColor: #F20D6B;
|
||||||
$okColor: #0DF279;
|
$okColor: #0DF279;
|
||||||
|
$errorColor: red;
|
||||||
|
|
||||||
|
|
||||||
// ----- LIGHT-THEME --------
|
// ----- LIGHT-THEME --------
|
||||||
@ -44,4 +46,6 @@ $primaryColorHoverLight: lighten($primaryColorLight, 10%);
|
|||||||
$inputBackgroundColorLight: #FFFFFF;
|
$inputBackgroundColorLight: #FFFFFF;
|
||||||
$buttonBackgroundColorHoverLight: $inputBackgroundColorLight;
|
$buttonBackgroundColorHoverLight: $inputBackgroundColorLight;
|
||||||
$profileImgLight: "../../../images/icon-user-light";
|
$profileImgLight: "../../../images/icon-user-light";
|
||||||
$otherUserImgLight: "../../../images/icon-newuser-light";
|
$otherUserImgLight: "../../../images/icon-newuser-light";
|
||||||
|
$nokColorLight: #F20D6B;
|
||||||
|
$okColorLight: #50CA3D;
|
@ -73,7 +73,7 @@
|
|||||||
*, *::before, *::after {
|
*, *::before, *::after {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: Lato;
|
font-family: Lato;
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,6 +98,7 @@ h1 {
|
|||||||
font-family: Lato;
|
font-family: Lato;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@ -123,7 +124,13 @@ header .logo {
|
|||||||
margin: 30px;
|
margin: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.head {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content form {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -138,11 +145,14 @@ a {
|
|||||||
a:hover {
|
a:hover {
|
||||||
color: #5b8fd3;
|
color: #5b8fd3;
|
||||||
}
|
}
|
||||||
|
a.tos-link {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button, .button {
|
||||||
background-color: #282828;
|
background-color: #282828;
|
||||||
color: #3574C6;
|
color: #3574C6;
|
||||||
border: 2px solid #3574C6;
|
border: 1px solid #3574C6;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
@ -150,36 +160,39 @@ button {
|
|||||||
transition: all 0.3s ease 0s;
|
transition: all 0.3s ease 0s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 40px;
|
||||||
}
|
}
|
||||||
button:hover {
|
button:hover, .button:hover {
|
||||||
background-color: #5b8fd3;
|
background-color: #5b8fd3;
|
||||||
color: #282828;
|
color: #282828;
|
||||||
border: 2px solid #5b8fd3;
|
border: 1px solid #5b8fd3;
|
||||||
}
|
}
|
||||||
button.primary {
|
button.primary, .button.primary {
|
||||||
background-color: #3574C6;
|
background-color: #3574C6;
|
||||||
color: #BBBBC8;
|
color: #BBBBC8;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
button.primary:hover {
|
button.primary:hover, .button.primary:hover {
|
||||||
background-color: #5b8fd3;
|
background-color: #5b8fd3;
|
||||||
}
|
}
|
||||||
button:disabled {
|
button:disabled, .button:disabled {
|
||||||
background-color: #505050;
|
background-color: #999999;
|
||||||
border: 2px solid #505050;
|
border: 1px solid #999999;
|
||||||
}
|
}
|
||||||
button:disabled:hover {
|
button:disabled:hover, .button:disabled:hover {
|
||||||
background-color: #505050;
|
background-color: #999999;
|
||||||
border: 2px solid #505050;
|
border: 1px solid #999999;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:not([type=radio]), select {
|
input:not([type=radio]), select {
|
||||||
background-color: #252525;
|
background-color: #252525;
|
||||||
color: #BBBBC8;
|
color: #BBBBC8;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
border: 2px solid #505050;
|
border: 1px solid #999999;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding-left: 15px;
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
form button.user-selection .profile-image, .login-profile .profile-image {
|
form button.user-selection .profile-image, .login-profile .profile-image {
|
||||||
@ -214,7 +227,7 @@ form button.user-selection:hover .profile-image, .login-profile:hover .profile-i
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.login-profile .names div:first-of-type {
|
.login-profile .names div:first-of-type {
|
||||||
font-size: 40px;
|
font-size: 26px;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
.login-profile .names div:nth-of-type(2) {
|
.login-profile .names div:nth-of-type(2) {
|
||||||
@ -245,20 +258,21 @@ form .field.check-box {
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
form .field.check-box input[type=checkbox] {
|
form .field.check-box input[type=checkbox] {
|
||||||
height: 20px;
|
height: 16px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
form .field.check-box label {
|
form .field.check-box label {
|
||||||
height: 20px;
|
height: 16px;
|
||||||
text-transform: inherit;
|
text-transform: inherit;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 2px 0 0 15px;
|
padding: 2px 0 0 15px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
color: #BBBBC8;
|
||||||
}
|
}
|
||||||
form label {
|
form label {
|
||||||
color: #898989;
|
color: #898989;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 0.9rem;
|
font-size: 0.8rem;
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
}
|
}
|
||||||
form label span.optional {
|
form label span.optional {
|
||||||
@ -302,7 +316,7 @@ form button.user-selection .sessionstate {
|
|||||||
height: 20px;
|
height: 20px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
border-color: #505050;
|
border-color: #999999;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -363,6 +377,7 @@ form ul#passwordcomplexity {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
form ul#passwordcomplexity li {
|
form ul#passwordcomplexity li {
|
||||||
flex: 1 0 50%;
|
flex: 1 0 50%;
|
||||||
@ -444,4 +459,8 @@ footer {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #F20D6B;
|
||||||
|
}
|
||||||
|
|
||||||
/*# sourceMappingURL=dark.css.map */
|
/*# sourceMappingURL=dark.css.map */
|
||||||
|
@ -1 +1 @@
|
|||||||
{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/variables.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCHW;EDIX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCOc;EDNd,OCOQ;EDNR;EACA;EACA;EAEI;;;AAIR;EACI,OCHQ;EDIR,aC3BS;ED4BT;EACA,WCzBS;;;AD4Bb;EACI,OCVQ;EDWR,aClCS;EDmCT;EACA,WC/BU;;;ADkCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OCxCW;EDyCX;EACA;;AAEA;EACI,OC5CY;;;ADgDpB;EACI,kBCpDc;EDqDd,OCnDW;EDoDX;EACA;EACA;EACA;EACA,QC7EU;ED8EV;EACA;EACA;;AACA;EACI,kBC5DY;ED6DZ,OChEU;EDiEV;;AAGJ;EACI,kBCnEO;EDoEP,OCrEI;EDsEJ;;AACA;EACI,kBCtEQ;;AD0EhB;EACI,kBCzEW;ED0EX;;AAEA;EACI,kBC7EO;ED8EP;;;AAKZ;EACI,kBCnFmB;EDoFnB,OCzFQ;ED0FR,QC9GU;ED+GV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;EC9GN;;AACA;EDyGE;ICxGA;IACA;;;AD+GA;EClHF;;AACA;EDiHE;IChHA;IACA;;;;ADsHA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WC7IC;ED8ID;;AAGJ;EACI;EACA;EACA;EACA,OC/HC;;;ADqIT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;;AAIR;EACI,OC5KK;ED6KL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OCzMI;ED0MJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBC5MW;;AD+Mf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cCrOO;EDsOP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OCjQP;;ADwQL;EACI;;AAEJ;EACI;EACA;EACA;EACA;EC3RV;;AACA;EDsRM;ICrRJ;IACA;;;AD6RQ;EACI;EACA;EClSd;;AACA;ED+RU;IC9RR;IACA;;;ADoSI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OC3SN;;ADgTE;EACI,OClTL;;ADuTP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MClVI;;ADqVR;EACI,MCvVU;;;AD4Vd;EACI;EACA;;;AAIR;EAII;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA","file":"dark.css"}
|
{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/variables.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCHW;EDIX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCQc;EDPd,OCQQ;EDPR;EACA;EACA;EAEI;;;AAIR;EACI,OCFQ;EDGR,aC3BS;ED4BT;EACA,WCzBS;ED0BT;;;AAGJ;EACI,OCVQ;EDWR,aCnCS;EDoCT;EACA,WChCU;;;ADmCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OC9CW;ED+CX;EACA;;AAEA;EACI,OClDY;;ADqDhB;EACI;;;AAIR;EACI,kBC9Dc;ED+Dd,OC7DW;ED8DX;EACA;EACA;EACA;EACA,QCxFU;EDyFV;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBC1EY;ED2EZ,OC9EU;ED+EV;;AAGJ;EACI,kBCjFO;EDkFP,OCnFI;EDoFJ;;AACA;EACI,kBCpFQ;;ADwFhB;EACI,kBCvFW;EDwFX;;AAEA;EACI,kBC3FO;ED4FP;;;AAOZ;EACI,kBCnGmB;EDoGnB,OCzGQ;ED0GR,QC/HU;EDgIV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;EC9HN;;AACA;EDyHE;ICxHA;IACA;;;AD+HA;EClIF;;AACA;EDiIE;IChIA;IACA;;;;ADsIA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WC5JE;ED6JF;;AAGJ;EACI;EACA;EACA;EACA,OC/IC;;;ADqJT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OC3LA;;AD+LR;EACI,OC7LK;ED8LL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OC1NI;ED2NJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBC7NW;;ADgOf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cCtPO;EDuPP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OClRP;;ADyRL;EACI;;AAEJ;EACI;EACA;EACA;EACA;EC5SV;;AACA;EDuSM;ICtSJ;IACA;;;AD8SQ;EACI;EACA;ECnTd;;AACA;EDgTU;IC/SR;IACA;;;ADqTI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OC7TN;;ADkUE;EACI,OCpUL;;ADyUP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MCpWI;;ADuWR;EACI,MCzWU;;;AD8Wd;EACI;EACA;;;AAIR;EAII;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OCpZO","file":"dark.css"}
|
@ -73,7 +73,7 @@
|
|||||||
*, *::before, *::after {
|
*, *::before, *::after {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: Lato;
|
font-family: Lato;
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,6 +98,7 @@ h1 {
|
|||||||
font-family: Lato;
|
font-family: Lato;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@ -123,7 +124,13 @@ header .logo {
|
|||||||
margin: 30px;
|
margin: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.head {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content form {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -138,11 +145,14 @@ a {
|
|||||||
a:hover {
|
a:hover {
|
||||||
color: #5b8fd3;
|
color: #5b8fd3;
|
||||||
}
|
}
|
||||||
|
a.tos-link {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button, .button {
|
||||||
background-color: #282828;
|
background-color: #282828;
|
||||||
color: #3574C6;
|
color: #3574C6;
|
||||||
border: 2px solid #3574C6;
|
border: 1px solid #3574C6;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
@ -150,36 +160,39 @@ button {
|
|||||||
transition: all 0.3s ease 0s;
|
transition: all 0.3s ease 0s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 40px;
|
||||||
}
|
}
|
||||||
button:hover {
|
button:hover, .button:hover {
|
||||||
background-color: #5b8fd3;
|
background-color: #5b8fd3;
|
||||||
color: #282828;
|
color: #282828;
|
||||||
border: 2px solid #5b8fd3;
|
border: 1px solid #5b8fd3;
|
||||||
}
|
}
|
||||||
button.primary {
|
button.primary, .button.primary {
|
||||||
background-color: #3574C6;
|
background-color: #3574C6;
|
||||||
color: #BBBBC8;
|
color: #BBBBC8;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
button.primary:hover {
|
button.primary:hover, .button.primary:hover {
|
||||||
background-color: #5b8fd3;
|
background-color: #5b8fd3;
|
||||||
}
|
}
|
||||||
button:disabled {
|
button:disabled, .button:disabled {
|
||||||
background-color: #505050;
|
background-color: #999999;
|
||||||
border: 2px solid #505050;
|
border: 1px solid #999999;
|
||||||
}
|
}
|
||||||
button:disabled:hover {
|
button:disabled:hover, .button:disabled:hover {
|
||||||
background-color: #505050;
|
background-color: #999999;
|
||||||
border: 2px solid #505050;
|
border: 1px solid #999999;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:not([type=radio]), select {
|
input:not([type=radio]), select {
|
||||||
background-color: #252525;
|
background-color: #252525;
|
||||||
color: #BBBBC8;
|
color: #BBBBC8;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
border: 2px solid #505050;
|
border: 1px solid #999999;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding-left: 15px;
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
form button.user-selection .profile-image, .login-profile .profile-image {
|
form button.user-selection .profile-image, .login-profile .profile-image {
|
||||||
@ -214,7 +227,7 @@ form button.user-selection:hover .profile-image, .login-profile:hover .profile-i
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.login-profile .names div:first-of-type {
|
.login-profile .names div:first-of-type {
|
||||||
font-size: 40px;
|
font-size: 26px;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
.login-profile .names div:nth-of-type(2) {
|
.login-profile .names div:nth-of-type(2) {
|
||||||
@ -245,20 +258,21 @@ form .field.check-box {
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
form .field.check-box input[type=checkbox] {
|
form .field.check-box input[type=checkbox] {
|
||||||
height: 20px;
|
height: 16px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
form .field.check-box label {
|
form .field.check-box label {
|
||||||
height: 20px;
|
height: 16px;
|
||||||
text-transform: inherit;
|
text-transform: inherit;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 2px 0 0 15px;
|
padding: 2px 0 0 15px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
color: #BBBBC8;
|
||||||
}
|
}
|
||||||
form label {
|
form label {
|
||||||
color: #898989;
|
color: #898989;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 0.9rem;
|
font-size: 0.8rem;
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
}
|
}
|
||||||
form label span.optional {
|
form label span.optional {
|
||||||
@ -302,7 +316,7 @@ form button.user-selection .sessionstate {
|
|||||||
height: 20px;
|
height: 20px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
border-color: #505050;
|
border-color: #999999;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -363,6 +377,7 @@ form ul#passwordcomplexity {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
form ul#passwordcomplexity li {
|
form ul#passwordcomplexity li {
|
||||||
flex: 1 0 50%;
|
flex: 1 0 50%;
|
||||||
@ -444,6 +459,10 @@ footer {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #F20D6B;
|
||||||
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
color: #282828;
|
color: #282828;
|
||||||
@ -455,66 +474,75 @@ html header .logo {
|
|||||||
html h1, html h2 {
|
html h1, html h2 {
|
||||||
color: #282828;
|
color: #282828;
|
||||||
}
|
}
|
||||||
html button {
|
html button, html .button {
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
color: #3574C6;
|
color: #3574C6;
|
||||||
border: 2px solid #3574C6;
|
border: 1px solid #3574C6;
|
||||||
}
|
}
|
||||||
html button:hover {
|
html button:hover, html .button:hover {
|
||||||
background-color: #5b8fd3;
|
background-color: #5b8fd3;
|
||||||
border: 2px solid #5b8fd3;
|
border: 1px solid #5b8fd3;
|
||||||
|
color: #FFFFFF;
|
||||||
}
|
}
|
||||||
html button.primary {
|
html button.primary, html .button.primary {
|
||||||
background-color: #3574C6;
|
background-color: #3574C6;
|
||||||
color: #BBBBC8;
|
color: #FFFFFF;
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: 0px 10px 30px #3574C6;
|
box-shadow: 0px 10px 30px #3574C6;
|
||||||
}
|
}
|
||||||
html button.primary:hover {
|
html button.primary:hover, html .button.primary:hover {
|
||||||
background-color: #5b8fd3;
|
background-color: #5b8fd3;
|
||||||
}
|
}
|
||||||
html button.clean {
|
html button:disabled, html .button:disabled {
|
||||||
|
background-color: #999999;
|
||||||
|
border: 1px solid #999999;
|
||||||
|
}
|
||||||
|
html button:disabled:hover, html .button:disabled:hover {
|
||||||
|
background-color: #999999;
|
||||||
|
border: 1px solid #999999;
|
||||||
|
}
|
||||||
|
html button.clean, html .button.clean {
|
||||||
color: #282828;
|
color: #282828;
|
||||||
}
|
}
|
||||||
html button.clean:hover {
|
html button.clean:hover, html .button.clean:hover {
|
||||||
border: none;
|
border: none;
|
||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
}
|
}
|
||||||
html button.user-selection .profile-image {
|
html button.user-selection .profile-image, html .button.user-selection .profile-image {
|
||||||
background-image: url("../../../images/icon-user-light.png");
|
background-image: url("../../../images/icon-user-light.png");
|
||||||
}
|
}
|
||||||
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
||||||
html button.user-selection .profile-image {
|
html button.user-selection .profile-image, html .button.user-selection .profile-image {
|
||||||
background-image: url("../../../images/icon-user-light@2x.png");
|
background-image: url("../../../images/icon-user-light@2x.png");
|
||||||
background-size: 80px 80px;
|
background-size: 80px 80px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
html button.user-selection:hover {
|
html button.user-selection:hover, html .button.user-selection:hover {
|
||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
}
|
}
|
||||||
html button.user-selection:hover .profile-image {
|
html button.user-selection:hover .profile-image, html .button.user-selection:hover .profile-image {
|
||||||
background-image: url("../../../images/icon-user-light-hover.png");
|
background-image: url("../../../images/icon-user-light-hover.png");
|
||||||
}
|
}
|
||||||
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
||||||
html button.user-selection:hover .profile-image {
|
html button.user-selection:hover .profile-image, html .button.user-selection:hover .profile-image {
|
||||||
background-image: url("../../../images/icon-user-light-hover@2x.png");
|
background-image: url("../../../images/icon-user-light-hover@2x.png");
|
||||||
background-size: 80px 80px;
|
background-size: 80px 80px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
html button.other-user .other-user-image {
|
html button.other-user .other-user-image, html .button.other-user .other-user-image {
|
||||||
background-image: url("../../../images/icon-newuser-light.png");
|
background-image: url("../../../images/icon-newuser-light.png");
|
||||||
}
|
}
|
||||||
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
||||||
html button.other-user .other-user-image {
|
html button.other-user .other-user-image, html .button.other-user .other-user-image {
|
||||||
background-image: url("../../../images/icon-newuser-light@2x.png");
|
background-image: url("../../../images/icon-newuser-light@2x.png");
|
||||||
background-size: 80px 60px;
|
background-size: 80px 60px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
html button.other-user:hover .other-user-image {
|
html button.other-user:hover .other-user-image, html .button.other-user:hover .other-user-image {
|
||||||
background-image: url("../../../images/icon-newuser-light-hover.png");
|
background-image: url("../../../images/icon-newuser-light-hover.png");
|
||||||
}
|
}
|
||||||
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
||||||
html button.other-user:hover .other-user-image {
|
html button.other-user:hover .other-user-image, html .button.other-user:hover .other-user-image {
|
||||||
background-image: url("../../../images/icon-newuser-light-hover@2x.png");
|
background-image: url("../../../images/icon-newuser-light-hover@2x.png");
|
||||||
background-size: 80px 60px;
|
background-size: 80px 60px;
|
||||||
}
|
}
|
||||||
@ -529,5 +557,41 @@ html #qrcode svg rect.color {
|
|||||||
html #qrcode svg rect.bg-color {
|
html #qrcode svg rect.bg-color {
|
||||||
fill: #f5f5f5;
|
fill: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
form .field.check-box label {
|
||||||
|
color: #282828;
|
||||||
|
}
|
||||||
|
form ul#passwordcomplexity li i {
|
||||||
|
color: #50CA3D;
|
||||||
|
}
|
||||||
|
form ul#passwordcomplexity li.invalid i {
|
||||||
|
color: #F20D6B;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-profile .profile-image, form button.user-selection .profile-image {
|
||||||
|
background-image: url("../../../images/icon-user-light.png");
|
||||||
|
}
|
||||||
|
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
||||||
|
.login-profile .profile-image, form button.user-selection .profile-image {
|
||||||
|
background-image: url("../../../images/icon-user-light@2x.png");
|
||||||
|
background-size: 80px 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.login-profile:hover .profile-image, form button.user-selection:hover .profile-image {
|
||||||
|
background-image: url("../../../images/icon-user-light-hover.png");
|
||||||
|
}
|
||||||
|
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
||||||
|
.login-profile:hover .profile-image, form button.user-selection:hover .profile-image {
|
||||||
|
background-image: url("../../../images/icon-user-light-hover@2x.png");
|
||||||
|
background-size: 80px 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.free-tier {
|
||||||
|
border: 2px solid #F20D6B;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #F20D6B;
|
||||||
|
}
|
||||||
|
|
||||||
/*# sourceMappingURL=light.css.map */
|
/*# sourceMappingURL=light.css.map */
|
||||||
|
@ -1 +1 @@
|
|||||||
{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/variables.scss","../../scss/light.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCHW;EDIX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCOc;EDNd,OCOQ;EDNR;EACA;EACA;EAEI;;;AAIR;EACI,OCHQ;EDIR,aC3BS;ED4BT;EACA,WCzBS;;;AD4Bb;EACI,OCVQ;EDWR,aClCS;EDmCT;EACA,WC/BU;;;ADkCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OCxCW;EDyCX;EACA;;AAEA;EACI,OC5CY;;;ADgDpB;EACI,kBCpDc;EDqDd,OCnDW;EDoDX;EACA;EACA;EACA;EACA,QC7EU;ED8EV;EACA;EACA;;AACA;EACI,kBC5DY;ED6DZ,OChEU;EDiEV;;AAGJ;EACI,kBCnEO;EDoEP,OCrEI;EDsEJ;;AACA;EACI,kBCtEQ;;AD0EhB;EACI,kBCzEW;ED0EX;;AAEA;EACI,kBC7EO;ED8EP;;;AAKZ;EACI,kBCnFmB;EDoFnB,OCzFQ;ED0FR,QC9GU;ED+GV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;EC9GN;;AACA;EDyGE;ICxGA;IACA;;;AD+GA;EClHF;;AACA;EDiHE;IChHA;IACA;;;;ADsHA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WC7IC;ED8ID;;AAGJ;EACI;EACA;EACA;EACA,OC/HC;;;ADqIT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;;AAIR;EACI,OC5KK;ED6KL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OCzMI;ED0MJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBC5MW;;AD+Mf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cCrOO;EDsOP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OCjQP;;ADwQL;EACI;;AAEJ;EACI;EACA;EACA;EACA;EC3RV;;AACA;EDsRM;ICrRJ;IACA;;;AD6RQ;EACI;EACA;EClSd;;AACA;ED+RU;IC9RR;IACA;;;ADoSI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OC3SN;;ADgTE;EACI,OClTL;;ADuTP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MClVI;;ADqVR;EACI,MCvVU;;;AD4Vd;EACI;EACA;;;AAIR;EAII;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AE/ZJ;EACI,kBDqCmB;ECpCnB,ODqBc;ECnBV;;AAGJ;EACI;;AAGJ;EACI,ODWU;;ACRd;EACI,kBDsBe;ECrBf,ODQO;ECPP;;AAEA;EACI,kBDoBa;ECnBb;;AAGJ;EACI,kBDDG;ECEH,ODHA;ECIA;EACA;;AACA;EACI,kBDLI;;ACSZ;EACI,ODbM;;ACeN;EACI;EACA,kBDEY;;ACGhB;ED9BV;;AACA;EC6BU;ID5BR;IACA;;;AC+BQ;EACI,kBDRY;;ACUZ;EDrCd;;AACA;ECoCc;IDnCZ;IACA;;;ACyCQ;ED5CV;;AACA;EC2CU;ID1CR;IACA;;;AC8CY;EDjDd;;AACA;ECgDc;ID/CZ;IACA;;;ACqDA;EACI,kBD9BoB;EC+BpB,ODlDU;;ACsDV;EACI,MDvDM;;AC0DV;EACI,MD5CW","file":"light.css"}
|
{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/variables.scss","../../scss/light.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCHW;EDIX;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA,kBCQc;EDPd,OCQQ;EDPR;EACA;EACA;EAEI;;;AAIR;EACI,OCFQ;EDGR,aC3BS;ED4BT;EACA,WCzBS;ED0BT;;;AAGJ;EACI,OCVQ;EDWR,aCnCS;EDoCT;EACA,WChCU;;;ADmCd;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OC9CW;ED+CX;EACA;;AAEA;EACI,OClDY;;ADqDhB;EACI;;;AAIR;EACI,kBC9Dc;ED+Dd,OC7DW;ED8DX;EACA;EACA;EACA;EACA,QCxFU;EDyFV;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI,kBC1EY;ED2EZ,OC9EU;ED+EV;;AAGJ;EACI,kBCjFO;EDkFP,OCnFI;EDoFJ;;AACA;EACI,kBCpFQ;;ADwFhB;EACI,kBCvFW;EDwFX;;AAEA;EACI,kBC3FO;ED4FP;;;AAOZ;EACI,kBCnGmB;EDoGnB,OCzGQ;ED0GR,QC/HU;EDgIV;EACA;EACA;;;AAIA;EACI;EACA;EACA;EACA;EC9HN;;AACA;EDyHE;ICxHA;IACA;;;AD+HA;EClIF;;AACA;EDiIE;IChIA;IACA;;;;ADsIA;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI,WC5JE;ED6JF;;AAGJ;EACI;EACA;EACA;EACA,OC/IC;;;ADqJT;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,OC3LA;;AD+LR;EACI,OC7LK;ED8LL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;EACA;EACA,OC1NI;ED2NJ;EACA;EACA;EACA;;AAEA;EACI;EACA,kBC7NW;;ADgOf;EACI;;AAIR;EACI;;AAKA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA,cCtPO;EDuPP;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAKR;EACI;;AAEA;EACI;;AAEA;EACI;;AAEJ;EACI,OClRP;;ADyRL;EACI;;AAEJ;EACI;EACA;EACA;EACA;EC5SV;;AACA;EDuSM;ICtSJ;IACA;;;AD8SQ;EACI;EACA;ECnTd;;AACA;EDgTU;IC/SR;IACA;;;ADqTI;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA,OC7TN;;ADkUE;EACI,OCpUL;;ADyUP;EACI;;AACA;EACI;EACA;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI,MCpWI;;ADuWR;EACI,MCzWU;;;AD8Wd;EACI;EACA;;;AAIR;EAII;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI,OCpZO;;;AClCX;EACI,kBDuCmB;ECtCnB,ODsBc;ECpBV;;AAGJ;EACI;;AAGJ;EACI,ODYU;;ACTd;EACI,kBDwBe;ECvBf,ODSO;ECRP;;AAEA;EACI,kBDsBa;ECrBb;EACA,ODqBgB;;AClBpB;EACI,kBDDG;ECEH,ODgBgB;ECfhB;EACA;;AACA;EACI,kBDLI;;ACSZ;EACI,kBDRO;ECSP;;AAEA;EACI,kBDZG;ECaH;;AAIR;EACI,ODvBM;;ACyBN;EACI;EACA,kBDPY;;ACYhB;EDxCV;;AACA;ECuCU;IDtCR;IACA;;;ACyCQ;EACI,kBDjBY;;ACmBZ;ED/Cd;;AACA;EC8Cc;ID7CZ;IACA;;;ACmDQ;EDtDV;;AACA;ECqDU;IDpDR;IACA;;;ACwDY;ED3Dd;;AACA;EC0Dc;IDzDZ;IACA;;;AC+DA;EACI,kBDvCoB;ECwCpB,OD5DU;;ACgEV;EACI,MDjEM;;ACoEV;EACI,MDrDW;;ACiEnB;EACI,ODlFU;;ACsFb;EACI,OD9DM;;ACkEN;EACI,ODpEG;;;AC4EZ;ED5GF;;AACA;EC2GE;ID1GA;IACA;;;AC6GA;EDhHF;;AACA;EC+GE;ID9GA;IACA;;;;ACkHJ;EACI;;;AAGJ;EACI,OD1FY","file":"light.css"}
|
@ -1,8 +1,10 @@
|
|||||||
{{template "main-top" .}}
|
{{template "main-top" .}}
|
||||||
|
|
||||||
{{ template "user-profile" . }}
|
<div class="head">
|
||||||
|
{{ template "user-profile" . }}
|
||||||
|
|
||||||
<p>{{t "PasswordChange.Description"}}</p>
|
<p>{{t "PasswordChange.Description"}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form action="{{ changePasswordUrl }}" method="POST">
|
<form action="{{ changePasswordUrl }}" method="POST">
|
||||||
|
|
||||||
@ -42,8 +44,8 @@
|
|||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button type="submit" id="change-password-button" name="resend" value="false" class="primary right">{{t "Actions.Next"}}</button>
|
<button type="submit" id="change-password-button" name="resend" value="false" class="primary right">{{t "Actions.Next"}}</button>
|
||||||
<a href="{{ loginUrl }}">
|
<a class="button secondary" href="{{ loginUrl }}">
|
||||||
<button class="secondary" type="button">{{t "Actions.Cancel"}}</button>
|
{{t "Actions.Cancel"}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
{{template "main-top" .}}
|
{{template "main-top" .}}
|
||||||
|
|
||||||
{{ template "user-profile" . }}
|
<div class="head">
|
||||||
|
{{ template "user-profile" . }}
|
||||||
|
|
||||||
<p>{{t "PasswordChangeDone.Description"}}</p>
|
<p>{{t "PasswordChangeDone.Description"}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form action="{{ loginUrl }}" method="POST">
|
<form action="{{ loginUrl }}" method="POST">
|
||||||
|
|
||||||
@ -13,8 +15,8 @@
|
|||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
||||||
<a href="{{ loginUrl }}">
|
<a class="button secondary" href="{{ loginUrl }}">
|
||||||
<button class="secondary" type="button">{{t "Actions.Cancel"}}</button>
|
{{t "Actions.Cancel"}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
{{template "main-top" .}}
|
{{template "main-top" .}}
|
||||||
|
|
||||||
{{ template "user-profile" . }}
|
<div class="head">
|
||||||
|
{{ template "user-profile" . }}
|
||||||
|
|
||||||
<p>{{t "UsernameChange.Description"}}</p>
|
<p>{{t "UsernameChange.Description"}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form action="{{ changeUsernameUrl }}" method="POST">
|
<form action="{{ changeUsernameUrl }}" method="POST">
|
||||||
|
|
||||||
@ -21,8 +23,8 @@
|
|||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button type="submit" id="submit-button" value="false" class="primary right">{{t "Actions.Next"}}</button>
|
<button type="submit" id="submit-button" value="false" class="primary right">{{t "Actions.Next"}}</button>
|
||||||
<a href="{{ loginUrl }}">
|
<a class="button secondary" href="{{ loginUrl }}">
|
||||||
<button class="secondary" type="button">{{t "Actions.Cancel"}}</button>
|
{{t "Actions.Cancel"}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
{{template "main-top" .}}
|
{{template "main-top" .}}
|
||||||
|
|
||||||
{{ template "user-profile" . }}
|
<div class="head">
|
||||||
|
{{ template "user-profile" . }}
|
||||||
|
|
||||||
<p>{{t "UsernameChangeDone.Description"}}</p>
|
<p>{{t "UsernameChangeDone.Description"}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form action="{{ loginUrl }}" method="POST">
|
<form action="{{ loginUrl }}" method="POST">
|
||||||
|
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
{{template "main-top" .}}
|
||||||
|
|
||||||
|
<div class="head">
|
||||||
|
<h1>{{t "ExternalNotFoundOption.Title"}}</h1>
|
||||||
|
<p>{{t "ExternalNotFoundOption.Description"}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="{{ externalNotFoundOptionUrl }}" method="POST">
|
||||||
|
|
||||||
|
{{ .CSRF }}
|
||||||
|
|
||||||
|
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button class="secondary right" name="link" value="true" formnovalidate>{{t "ExternalNotFoundOption.Link"}}</button>
|
||||||
|
<button class="secondary right" name="autoregister" value="true" formnovalidate>{{t "ExternalNotFoundOption.AutoRegister"}}</button>
|
||||||
|
<a class="button secondary" href="{{ loginUrl .AuthReqID }}">
|
||||||
|
{{t "Actions.Back"}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{template "error-message" .}}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="{{ resourceUrl "scripts/form_submit.js" }}"></script>
|
||||||
|
<script src="{{ resourceUrl "scripts/default_form_validation.js" }}"></script>
|
||||||
|
|
||||||
|
{{template "main-bottom" .}}
|
||||||
|
|
@ -1,8 +1,10 @@
|
|||||||
{{template "main-top" .}}
|
{{template "main-top" .}}
|
||||||
|
|
||||||
{{ template "user-profile" . }}
|
<div class="head">
|
||||||
|
{{ template "user-profile" . }}
|
||||||
|
|
||||||
<p>{{t "InitPassword.Description" }}</p>
|
<p>{{t "InitPassword.Description" }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form action="{{ initPasswordUrl }}" method="POST">
|
<form action="{{ initPasswordUrl }}" method="POST">
|
||||||
|
|
||||||
@ -47,8 +49,8 @@
|
|||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button type="submit" id="init-button" name="resend" value="false" class="primary right" >{{t "Actions.Next"}}</button>
|
<button type="submit" id="init-button" name="resend" value="false" class="primary right" >{{t "Actions.Next"}}</button>
|
||||||
<button type="submit" name="resend" value="true" class="secondary right" formnovalidate>{{t "Actions.Resend" }}</button>
|
<button type="submit" name="resend" value="true" class="secondary right" formnovalidate>{{t "Actions.Resend" }}</button>
|
||||||
<a href="{{ loginUrl }}">
|
<a class="button secondary" href="{{ loginUrl }}">
|
||||||
<button class="secondary" type="button">{{t "Actions.Cancel"}}</button>
|
{{t "Actions.Cancel"}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
{{template "main-top" .}}
|
{{template "main-top" .}}
|
||||||
|
|
||||||
{{ template "user-profile" . }}
|
<div class="head">
|
||||||
|
{{ template "user-profile" . }}
|
||||||
|
|
||||||
<p>{{t "PasswordSetDone.Description"}}</p>
|
<p>{{t "PasswordSetDone.Description"}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form action="{{ loginUrl }}" method="POST">
|
<form action="{{ loginUrl }}" method="POST">
|
||||||
|
|
||||||
@ -12,8 +14,8 @@
|
|||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
||||||
<a href="{{ loginUrl }}">
|
<a class="button secondary" href="{{ loginUrl }}">
|
||||||
<button class="secondary" type="button">{{t "Actions.Cancel"}}</button>
|
{{t "Actions.Cancel"}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
{{template "main-top" .}}
|
{{template "main-top" .}}
|
||||||
|
|
||||||
{{ template "user-profile" . }}
|
<div class="head">
|
||||||
|
{{ template "user-profile" . }}
|
||||||
|
|
||||||
<p>{{t "InitUser.Description" }}</p>
|
<p>{{t "InitUser.Description" }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form action="{{ initUserUrl }}" method="POST">
|
<form action="{{ initUserUrl }}" method="POST">
|
||||||
|
|
||||||
@ -49,8 +51,8 @@
|
|||||||
value="false"
|
value="false"
|
||||||
class="primary right">{{t "Actions.Next"}}</button>
|
class="primary right">{{t "Actions.Next"}}</button>
|
||||||
<button type="submit" name="resend" value="true" class="secondary right" formnovalidate>{{t "Actions.Resend" }}</button>
|
<button type="submit" name="resend" value="true" class="secondary right" formnovalidate>{{t "Actions.Resend" }}</button>
|
||||||
<a href="{{ loginUrl }}">
|
<a class="button secondary" href="{{ loginUrl }}">
|
||||||
<button class="secondary" type="button">{{t "Actions.Cancel"}}</button>
|
{{t "Actions.Cancel"}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
{{template "main-top" .}}
|
{{template "main-top" .}}
|
||||||
|
|
||||||
{{ template "user-profile" . }}
|
<div class="head">
|
||||||
|
{{ template "user-profile" . }}
|
||||||
|
|
||||||
<p>{{t "InitUserDone.Description"}}</p>
|
<p>{{t "InitUserDone.Description"}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form action="{{ loginUrl }}" method="POST">
|
<form action="{{ loginUrl }}" method="POST">
|
||||||
|
|
||||||
@ -12,8 +14,8 @@
|
|||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
||||||
<a href="{{ loginUrl }}">
|
<a class="button secondary" href="{{ loginUrl }}">
|
||||||
<button class="secondary" type="button">{{t "Actions.Cancel"}}</button>
|
{{t "Actions.Cancel"}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
25
internal/ui/login/static/templates/link_users_done.html
Normal file
25
internal/ui/login/static/templates/link_users_done.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{{template "main-top" .}}
|
||||||
|
|
||||||
|
<div class="head">
|
||||||
|
{{ template "user-profile" . }}
|
||||||
|
|
||||||
|
<p>{{t "LinkingUsersDone.Description"}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="{{ loginUrl }}" method="POST">
|
||||||
|
|
||||||
|
{{ .CSRF }}
|
||||||
|
|
||||||
|
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
||||||
|
<a class="button secondary" href="{{ loginUrl }}">
|
||||||
|
{{t "Actions.Cancel"}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
{{template "main-bottom" .}}
|
||||||
|
|
@ -1,8 +1,16 @@
|
|||||||
|
|
||||||
{{template "main-top" .}}
|
{{template "main-top" .}}
|
||||||
|
|
||||||
<h1>{{t "Login.Title"}}</h1>
|
<div class="head">
|
||||||
<p>{{t "Login.Description"}}</p>
|
{{if .Linking}}
|
||||||
|
<h1>{{t "Login.TitleLinking"}}</h1>
|
||||||
|
<p>{{t "Login.DescriptionLinking"}}</p>
|
||||||
|
{{else}}
|
||||||
|
<h1>{{t "Login.Title"}}</h1>
|
||||||
|
<p>{{t "Login.Description"}}</p>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<form action="{{ loginNameUrl }}" method="POST">
|
<form action="{{ loginNameUrl }}" method="POST">
|
||||||
|
|
||||||
@ -10,19 +18,36 @@
|
|||||||
|
|
||||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||||
|
|
||||||
|
{{if .LoginPolicy.AllowUsernamePassword }}
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="loginName">{{t "Login.Loginname"}}</label>
|
<label class="label" for="loginName">{{t "Login.Loginname"}}</label>
|
||||||
<input class="input" type="text" id="loginName" name="loginName" placeholder="{{t "Login.LoginnamePlaceHolder"}}" value="{{ .LoginName }}" autocomplete="username" autofocus required>
|
<input class="input" type="text" id="loginName" name="loginName" placeholder="{{t "Login.LoginnamePlaceHolder"}}" value="{{ .LoginName }}" autocomplete="username" autofocus required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
{{template "error-message" .}}
|
{{template "error-message" .}}
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="primary right" id="submit-button" type="submit">{{t "Actions.Next"}}</button>
|
<button class="primary" id="submit-button" type="submit">{{t "Actions.Next"}}</button>
|
||||||
<button class="secondary right" name="register" value="true" formnovalidate>{{t "Actions.Register"}}</button>
|
{{if .LoginPolicy.AllowRegister}}
|
||||||
|
<button class="secondary" name="register" value="true" formnovalidate>{{t "Actions.Register"}}</button>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{if .LoginPolicy.AllowExternalIDP}}
|
||||||
|
<div class="actions idp-providers">
|
||||||
|
<p>{{t "Login.ExternalLogin"}}</p>
|
||||||
|
|
||||||
|
{{ $reqid := .AuthReqID}}
|
||||||
|
{{range $provider := .IDPProviders}}
|
||||||
|
<a href="{{ externalIDPAuthURL $reqid $provider.IDPConfigID}}" class="button secondary idp-providers">
|
||||||
|
{{$provider.Name}}
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
{{template "main-top" .}}
|
{{template "main-top" .}}
|
||||||
|
|
||||||
|
<div class="head">
|
||||||
<h1>{{t "LogoutDone.Title"}}</h1>
|
<h1>{{t "LogoutDone.Title"}}</h1>
|
||||||
<p>{{t "LogoutDone.Description"}}</p>
|
<p> {{t "LogoutDone.Description"}}</p>
|
||||||
|
</div>
|
||||||
<form action="{{ loginUrl }}" method="POST">
|
<form action="{{ loginUrl }}" method="POST">
|
||||||
|
|
||||||
{{ .CSRF }}
|
{{ .CSRF }}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
{{template "main-top" .}}
|
{{template "main-top" .}}
|
||||||
|
|
||||||
{{ template "user-profile" . }}
|
<div class="head">
|
||||||
|
{{ template "user-profile" . }}
|
||||||
|
|
||||||
<p>{{t "EmailVerification.Description"}}</p>
|
<p>{{t "EmailVerification.Description"}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form action="{{ mailVerificationUrl }}" method="POST">
|
<form action="{{ mailVerificationUrl }}" method="POST">
|
||||||
|
|
||||||
@ -25,8 +27,8 @@
|
|||||||
{{ if .UserID }}
|
{{ if .UserID }}
|
||||||
<button type="submit" name="resend" value="true" class="secondary right" formnovalidate>{{t "Actions.Resend"}}</button>
|
<button type="submit" name="resend" value="true" class="secondary right" formnovalidate>{{t "Actions.Resend"}}</button>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<a href="{{ loginUrl }}">
|
<a class="button secondary" href="{{ loginUrl }}">
|
||||||
<button class="secondary" type="button">{{t "Actions.Cancel"}}</button>
|
{{t "Actions.Cancel"}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
{{template "main-top" .}}
|
{{template "main-top" .}}
|
||||||
|
|
||||||
{{ template "user-profile" . }}
|
<div class="head">
|
||||||
|
{{ template "user-profile" . }}
|
||||||
|
|
||||||
<p>{{t "EmailVerificationDone.Description"}}</p>
|
<p>{{t "EmailVerificationDone.Description"}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form action="{{ loginUrl }}" method="POST">
|
<form action="{{ loginUrl }}" method="POST">
|
||||||
|
|
||||||
@ -13,8 +15,8 @@
|
|||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="primary right" type="submit">{{if .AuthReqID }}{{t "Actions.Next"}}{{else}}{{t "Actions.Login"}}{{end}}</button>
|
<button class="primary right" type="submit">{{if .AuthReqID }}{{t "Actions.Next"}}{{else}}{{t "Actions.Login"}}{{end}}</button>
|
||||||
<a href="{{ loginUrl }}">
|
<a class="button secondary" href="{{ loginUrl }}">
|
||||||
<button class="secondary" type="button">{{t "Actions.Cancel"}}</button>
|
{{t "Actions.Cancel"}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
{{template "main-top" .}}
|
{{template "main-top" .}}
|
||||||
|
|
||||||
{{ template "user-profile" . }}
|
<div class="head">
|
||||||
|
{{ template "user-profile" . }}
|
||||||
|
|
||||||
<p>{{t "MfaInitDone.Description"}}</p>
|
<p>{{t "MfaInitDone.Description"}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form action="{{ loginUrl }}" method="POST">
|
<form action="{{ loginUrl }}" method="POST">
|
||||||
|
|
||||||
@ -13,8 +15,8 @@
|
|||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
||||||
<a href="{{ loginUrl }}">
|
<a class="button secondary" href="{{ loginUrl }}">
|
||||||
<button class="secondary" type="button">{{t "Actions.Cancel"}}</button>
|
{{t "Actions.Cancel"}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
{{template "main-top" .}}
|
{{template "main-top" .}}
|
||||||
|
|
||||||
{{ template "user-profile" . }}
|
<div class="head">
|
||||||
|
{{ template "user-profile" . }}
|
||||||
|
|
||||||
<p>{{t "MfaInitVerify.Description"}}</p>
|
<p>{{t "MfaInitVerify.Description"}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form action="{{ mfaInitVerifyUrl }}" method="POST">
|
<form action="{{ mfaInitVerifyUrl }}" method="POST">
|
||||||
|
|
||||||
@ -35,11 +37,11 @@
|
|||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="primary right" id="submit-button" type="submit">{{t "Actions.Next"}}</button>
|
<button class="primary right" id="submit-button" type="submit">{{t "Actions.Next"}}</button>
|
||||||
<a href="{{ mfaPromptChangeUrl .AuthReqID .MfaType }}">
|
<a class="button secondary" href="{{ mfaPromptChangeUrl .AuthReqID .MfaType }}">
|
||||||
<button class="secondary" type="button">{{t "Actions.Back"}}</button>
|
{{t "Actions.Back"}}
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ loginUrl }}">
|
<a class="button secondary" href="{{ loginUrl }}">
|
||||||
<button class="secondary" type="button">{{t "Actions.Cancel"}}</button>
|
{{t "Actions.Cancel"}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
{{template "main-top" .}}
|
{{template "main-top" .}}
|
||||||
|
|
||||||
{{ template "user-profile" . }}
|
<div class="head">
|
||||||
|
{{ template "user-profile" . }}
|
||||||
|
|
||||||
<p>{{t "MfaPrompt.Description"}}</p>
|
<p>{{t "MfaPrompt.Description"}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form action="{{ mfaPromptUrl }}" method="POST">
|
<form action="{{ mfaPromptUrl }}" method="POST">
|
||||||
|
|
||||||
@ -25,8 +27,8 @@
|
|||||||
{{if not .MfaRequired}}
|
{{if not .MfaRequired}}
|
||||||
<button class="default right" name="skip" value="true" type="submit" formnovalidate>{{t "Actions.Skip"}}</button>
|
<button class="default right" name="skip" value="true" type="submit" formnovalidate>{{t "Actions.Skip"}}</button>
|
||||||
{{end}}
|
{{end}}
|
||||||
<a href="{{ loginUrl }}">
|
<a class="button secondary" href="{{ loginUrl }}">
|
||||||
<button class="secondary" type="button">{{t "Actions.Cancel"}}</button>
|
{{t "Actions.Cancel"}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
{{template "main-top" .}}
|
{{template "main-top" .}}
|
||||||
|
|
||||||
{{ template "user-profile" . }}
|
<div class="head">
|
||||||
|
{{ template "user-profile" . }}
|
||||||
|
|
||||||
<p>{{t "MfaVerify.Description"}}</p>
|
<p>{{t "MfaVerify.Description"}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form action="{{ mfaVerifyUrl }}" method="POST">
|
<form action="{{ mfaVerifyUrl }}" method="POST">
|
||||||
|
|
||||||
@ -22,8 +24,8 @@
|
|||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="primary right" id="submit-button" type="submit">{{t "Actions.Next"}}</button>
|
<button class="primary right" id="submit-button" type="submit">{{t "Actions.Next"}}</button>
|
||||||
<a href="{{ loginUrl }}">
|
<a class="button secondary" href="{{ loginUrl }}">
|
||||||
<button class="secondary" type="button">{{t "Actions.Cancel"}}</button>
|
{{t "Actions.Cancel"}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user