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:
Fabi 2020-09-18 13:26:28 +02:00 committed by GitHub
parent 1d542a0c57
commit 320ddfa46d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
141 changed files with 30057 additions and 12535 deletions

View File

@ -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 {

View File

@ -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'

View File

@ -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>

View File

@ -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...)
} }

View File

@ -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)
}

View File

@ -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,

View 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 = "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)
}

View File

@ -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,

View File

@ -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 {

View File

@ -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,

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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,

View File

@ -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
} }

View File

@ -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
}

View File

@ -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) {

View File

@ -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
} }

View File

@ -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 {

View File

@ -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},
} }
} }

View 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)
}

View 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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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{

View 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)
}

View 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)
}

View 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)
}

View File

@ -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)
}

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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

View File

@ -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 ""
}

View File

@ -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 {

View File

@ -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

View File

@ -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}
} }

View File

@ -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 {

View File

@ -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
} }

View File

@ -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 {

View File

@ -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))

View File

@ -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:"-"`
} }

View File

@ -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),
} }
} }

View File

@ -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) {

View File

@ -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})

View File

@ -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
} }

View File

@ -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),
} }

View File

@ -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...)
} }

View File

@ -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)
} }

View File

@ -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},
} }
} }

View File

@ -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)
}

View 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 = "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)
}

View File

@ -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)

View File

@ -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) {

View File

@ -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
} }

View File

@ -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

View File

@ -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

View File

@ -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)
}

View 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
}

View 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
}

View 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)
}

View File

@ -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 {

View File

@ -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
} }

View File

@ -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)
}

View 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)
}

View File

@ -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

View 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)
}

View File

@ -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 {

View File

@ -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)

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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)

View File

@ -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)

View File

@ -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 */

View File

@ -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"}

View File

@ -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 */

View File

@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/caos/variables.scss","../../scss/variables.scss","../../scss/light.scss"],"names":[],"mappings":"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","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"}

View File

@ -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;
} }

View File

@ -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;
} }

View File

@ -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;

View File

@ -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 */

View File

@ -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"}

View File

@ -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 */

View File

@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/variables.scss","../../scss/light.scss"],"names":[],"mappings":"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","file":"light.css"} {"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/variables.scss","../../scss/light.scss"],"names":[],"mappings":"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","file":"light.css"}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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" .}}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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" .}}

View File

@ -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>

View File

@ -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 }}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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