feat: add apple as idp (#6442)

* feat: manage apple idp

* handle apple idp callback

* add tests for provider

* basic console implementation

* implement flow for login UI and add logos / styling

* tests

* cleanup

* add upload button

* begin i18n

* apple logo positioning, file upload component

* fix add apple instance idp

* add missing apple logos for login

* update to go 1.21

* fix slice compare

* revert permission changes

* concrete error messages

* translate login apple logo -y-2px

* change form parsing

* sign in button

* fix tests

* lint console

---------

Co-authored-by: peintnermax <max@caos.ch>
This commit is contained in:
Livio Spring
2023-08-31 08:39:16 +02:00
committed by GitHub
parent 0d94947d3c
commit e17b49e4ca
89 changed files with 4384 additions and 64 deletions

View File

@@ -405,6 +405,27 @@ func (s *Server) UpdateLDAPProvider(ctx context.Context, req *admin_pb.UpdateLDA
}, nil
}
func (s *Server) AddAppleProvider(ctx context.Context, req *admin_pb.AddAppleProviderRequest) (*admin_pb.AddAppleProviderResponse, error) {
id, details, err := s.command.AddInstanceAppleProvider(ctx, addAppleProviderToCommand(req))
if err != nil {
return nil, err
}
return &admin_pb.AddAppleProviderResponse{
Id: id,
Details: object_pb.DomainToAddDetailsPb(details),
}, nil
}
func (s *Server) UpdateAppleProvider(ctx context.Context, req *admin_pb.UpdateAppleProviderRequest) (*admin_pb.UpdateAppleProviderResponse, error) {
details, err := s.command.UpdateInstanceAppleProvider(ctx, req.Id, updateAppleProviderToCommand(req))
if err != nil {
return nil, err
}
return &admin_pb.UpdateAppleProviderResponse{
Details: object_pb.DomainToChangeDetailsPb(details),
}, nil
}
func (s *Server) DeleteProvider(ctx context.Context, req *admin_pb.DeleteProviderRequest) (*admin_pb.DeleteProviderResponse, error) {
details, err := s.command.DeleteInstanceProvider(ctx, req.Id)
if err != nil {

View File

@@ -440,3 +440,27 @@ func updateLDAPProviderToCommand(req *admin_pb.UpdateLDAPProviderRequest) comman
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
func addAppleProviderToCommand(req *admin_pb.AddAppleProviderRequest) command.AppleProvider {
return command.AppleProvider{
Name: req.Name,
ClientID: req.ClientId,
TeamID: req.TeamId,
KeyID: req.KeyId,
PrivateKey: req.PrivateKey,
Scopes: req.Scopes,
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
func updateAppleProviderToCommand(req *admin_pb.UpdateAppleProviderRequest) command.AppleProvider {
return command.AppleProvider{
Name: req.Name,
ClientID: req.ClientId,
TeamID: req.TeamId,
KeyID: req.KeyId,
PrivateKey: req.PrivateKey,
Scopes: req.Scopes,
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}

View File

@@ -414,6 +414,8 @@ func providerTypeToPb(idpType domain.IDPType) idp_pb.ProviderType {
return idp_pb.ProviderType_PROVIDER_TYPE_GITLAB_SELF_HOSTED
case domain.IDPTypeGoogle:
return idp_pb.ProviderType_PROVIDER_TYPE_GOOGLE
case domain.IDPTypeApple:
return idp_pb.ProviderType_PROVIDER_TYPE_APPLE
case domain.IDPTypeUnspecified:
return idp_pb.ProviderType_PROVIDER_TYPE_UNSPECIFIED
default:
@@ -470,6 +472,10 @@ func configToPb(config *query.IDPTemplate) *idp_pb.ProviderConfig {
ldapConfigToPb(providerConfig, config.LDAPIDPTemplate)
return providerConfig
}
if config.AppleIDPTemplate != nil {
appleConfigToPb(providerConfig, config.AppleIDPTemplate)
return providerConfig
}
return providerConfig
}
@@ -620,3 +626,14 @@ func ldapAttributesToPb(attributes idp.LDAPAttributes) *idp_pb.LDAPAttributes {
ProfileAttribute: attributes.ProfileAttribute,
}
}
func appleConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.AppleIDPTemplate) {
providerConfig.Config = &idp_pb.ProviderConfig_Apple{
Apple: &idp_pb.AppleConfig{
ClientId: template.ClientID,
TeamId: template.TeamID,
KeyId: template.KeyID,
Scopes: template.Scopes,
},
}
}

View File

@@ -397,6 +397,27 @@ func (s *Server) UpdateLDAPProvider(ctx context.Context, req *mgmt_pb.UpdateLDAP
}, nil
}
func (s *Server) AddAppleProvider(ctx context.Context, req *mgmt_pb.AddAppleProviderRequest) (*mgmt_pb.AddAppleProviderResponse, error) {
id, details, err := s.command.AddOrgAppleProvider(ctx, authz.GetCtxData(ctx).OrgID, addAppleProviderToCommand(req))
if err != nil {
return nil, err
}
return &mgmt_pb.AddAppleProviderResponse{
Id: id,
Details: object_pb.DomainToAddDetailsPb(details),
}, nil
}
func (s *Server) UpdateAppleProvider(ctx context.Context, req *mgmt_pb.UpdateAppleProviderRequest) (*mgmt_pb.UpdateAppleProviderResponse, error) {
details, err := s.command.UpdateOrgAppleProvider(ctx, authz.GetCtxData(ctx).OrgID, req.Id, updateAppleProviderToCommand(req))
if err != nil {
return nil, err
}
return &mgmt_pb.UpdateAppleProviderResponse{
Details: object_pb.DomainToChangeDetailsPb(details),
}, nil
}
func (s *Server) DeleteProvider(ctx context.Context, req *mgmt_pb.DeleteProviderRequest) (*mgmt_pb.DeleteProviderResponse, error) {
details, err := s.command.DeleteOrgProvider(ctx, authz.GetCtxData(ctx).OrgID, req.Id)
if err != nil {

View File

@@ -457,3 +457,27 @@ func updateLDAPProviderToCommand(req *mgmt_pb.UpdateLDAPProviderRequest) command
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
func addAppleProviderToCommand(req *mgmt_pb.AddAppleProviderRequest) command.AppleProvider {
return command.AppleProvider{
Name: req.Name,
ClientID: req.ClientId,
TeamID: req.TeamId,
KeyID: req.KeyId,
PrivateKey: req.PrivateKey,
Scopes: req.Scopes,
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
func updateAppleProviderToCommand(req *mgmt_pb.UpdateAppleProviderRequest) command.AppleProvider {
return command.AppleProvider{
Name: req.Name,
ClientID: req.ClientId,
TeamID: req.TeamId,
KeyID: req.KeyId,
PrivateKey: req.PrivateKey,
Scopes: req.Scopes,
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}

View File

@@ -16,6 +16,7 @@ import (
z_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/form"
"github.com/zitadel/zitadel/internal/idp"
"github.com/zitadel/zitadel/internal/idp/providers/apple"
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
"github.com/zitadel/zitadel/internal/idp/providers/github"
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
@@ -52,6 +53,9 @@ type externalIDPCallbackData struct {
Code string `schema:"code"`
Error string `schema:"error"`
ErrorDescription string `schema:"error_description"`
// Apple returns a user on first registration
User string `schema:"user"`
}
// CallbackURL generates the instance specific URL to the IDP callback handler
@@ -115,7 +119,7 @@ func (h *Handler) handleCallback(w http.ResponseWriter, r *http.Request) {
return
}
idpUser, idpSession, err := h.fetchIDPUser(ctx, provider, data.Code)
idpUser, idpSession, err := h.fetchIDPUser(ctx, provider, data.Code, data.User)
if err != nil {
cmdErr := h.commands.FailIDPIntent(ctx, intent, err.Error())
logging.WithFields("intent", intent.AggregateID).OnError(cmdErr).Error("failed to push failed event on idp intent")
@@ -214,7 +218,7 @@ func redirectToFailureURL(w http.ResponseWriter, r *http.Request, i *command.IDP
http.Redirect(w, r, i.FailureURL.String(), http.StatusFound)
}
func (h *Handler) fetchIDPUser(ctx context.Context, identityProvider idp.Provider, code string) (user idp.User, idpTokens idp.Session, err error) {
func (h *Handler) fetchIDPUser(ctx context.Context, identityProvider idp.Provider, code string, appleUser string) (user idp.User, idpTokens idp.Session, err error) {
var session idp.Session
switch provider := identityProvider.(type) {
case *oauth.Provider:
@@ -229,6 +233,8 @@ func (h *Handler) fetchIDPUser(ctx context.Context, identityProvider idp.Provide
session = &openid.Session{Provider: provider.Provider, Code: code}
case *google.Provider:
session = &openid.Session{Provider: provider.Provider, Code: code}
case *apple.Provider:
session = &apple.Session{Session: &openid.Session{Provider: provider.Provider, Code: code}, UserFormValue: appleUser}
case *jwt.Provider, *ldap.Provider:
return nil, nil, z_errs.ThrowInvalidArgument(nil, "IDP-52jmn", "Errors.ExternalIDP.IDPTypeNotImplemented")
default:

View File

@@ -3,9 +3,8 @@ package login
import (
"net/http"
"github.com/zitadel/zitadel/internal/domain"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/domain"
)
const (

View File

@@ -18,6 +18,7 @@ import (
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/idp"
"github.com/zitadel/zitadel/internal/idp/providers/apple"
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
"github.com/zitadel/zitadel/internal/idp/providers/github"
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
@@ -41,6 +42,9 @@ type externalIDPData struct {
type externalIDPCallbackData struct {
State string `schema:"state"`
Code string `schema:"code"`
// Apple returns a user on first registration
User string `schema:"user"`
}
type externalNotFoundOptionFormData struct {
@@ -159,6 +163,8 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai
provider, err = l.gitlabSelfHostedProvider(r.Context(), identityProvider)
case domain.IDPTypeGoogle:
provider, err = l.googleProvider(r.Context(), identityProvider)
case domain.IDPTypeApple:
provider, err = l.appleProvider(r.Context(), identityProvider)
case domain.IDPTypeLDAP:
provider, err = l.ldapProvider(r.Context(), identityProvider)
case domain.IDPTypeUnspecified:
@@ -180,6 +186,18 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai
http.Redirect(w, r, session.GetAuthURL(), http.StatusFound)
}
// handleExternalLoginCallbackForm handles the callback from a IDP with form_post.
// It will redirect to the "normal" callback endpoint with the form data as query parameter.
// This way cookies will be handled correctly (same site = lax).
func (l *Login) handleExternalLoginCallbackForm(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
l.renderLogin(w, r, nil, err)
return
}
http.Redirect(w, r, HandlerPrefix+EndpointExternalLoginCallback+"?"+r.Form.Encode(), 302)
}
// handleExternalLoginCallback handles the callback from a IDP
// and tries to extract the user with the provided data
func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Request) {
@@ -259,6 +277,13 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
return
}
session = &openid.Session{Provider: provider.(*google.Provider).Provider, Code: data.Code}
case domain.IDPTypeApple:
provider, err = l.appleProvider(r.Context(), identityProvider)
if err != nil {
l.externalAuthFailed(w, r, authReq, nil, nil, err)
return
}
session = &apple.Session{Session: &openid.Session{Provider: provider.(*apple.Provider).Provider, Code: data.Code}, UserFormValue: data.User}
case domain.IDPTypeJWT,
domain.IDPTypeLDAP,
domain.IDPTypeUnspecified:
@@ -936,6 +961,21 @@ func (l *Login) gitlabSelfHostedProvider(ctx context.Context, identityProvider *
)
}
func (l *Login) appleProvider(ctx context.Context, identityProvider *query.IDPTemplate) (*apple.Provider, error) {
privateKey, err := crypto.Decrypt(identityProvider.AppleIDPTemplate.PrivateKey, l.idpConfigAlg)
if err != nil {
return nil, err
}
return apple.New(
identityProvider.AppleIDPTemplate.ClientID,
identityProvider.AppleIDPTemplate.TeamID,
identityProvider.AppleIDPTemplate.KeyID,
l.baseURL(ctx)+EndpointExternalLoginCallbackFormPost,
privateKey,
identityProvider.AppleIDPTemplate.Scopes,
)
}
func (l *Login) appendUserGrants(ctx context.Context, userGrants []*domain.UserGrant, resourceOwner string) error {
if len(userGrants) == 0 {
return nil
@@ -971,6 +1011,8 @@ func tokens(session idp.Session) *oidc.Tokens[*oidc.IDTokenClaims] {
return s.Tokens
case *azuread.Session:
return s.Tokens
case *apple.Session:
return s.Tokens
}
return nil
}

View File

@@ -120,6 +120,12 @@ func createCSRFInterceptor(cookieName string, csrfCookieKey []byte, externalSecu
handler.ServeHTTP(w, r)
return
}
// ignore form post callback
// it will redirect to the "normal" callback, where the cookie is set again
if r.URL.Path == EndpointExternalLoginCallbackFormPost && r.Method == http.MethodPost {
handler.ServeHTTP(w, r)
return
}
csrf.Protect(csrfCookieKey,
csrf.Secure(externalSecure),
csrf.CookieName(http_utils.SetCookiePrefix(cookieName, "", path, externalSecure)),

View File

@@ -7,44 +7,45 @@ import (
)
const (
EndpointRoot = "/"
EndpointHealthz = "/healthz"
EndpointReadiness = "/ready"
EndpointLogin = "/login"
EndpointExternalLogin = "/login/externalidp"
EndpointExternalLoginCallback = "/login/externalidp/callback"
EndpointJWTAuthorize = "/login/jwt/authorize"
EndpointJWTCallback = "/login/jwt/callback"
EndpointLDAPLogin = "/login/ldap"
EndpointLDAPCallback = "/login/ldap/callback"
EndpointPasswordlessLogin = "/login/passwordless"
EndpointPasswordlessRegistration = "/login/passwordless/init"
EndpointPasswordlessPrompt = "/login/passwordless/prompt"
EndpointLoginName = "/loginname"
EndpointUserSelection = "/userselection"
EndpointChangeUsername = "/username/change"
EndpointPassword = "/password"
EndpointInitPassword = "/password/init"
EndpointChangePassword = "/password/change"
EndpointPasswordReset = "/password/reset"
EndpointInitUser = "/user/init"
EndpointMFAVerify = "/mfa/verify"
EndpointMFAPrompt = "/mfa/prompt"
EndpointMFAInitVerify = "/mfa/init/verify"
EndpointMFASMSInitVerify = "/mfa/init/sms/verify"
EndpointMFAOTPVerify = "/mfa/otp/verify"
EndpointMFAInitU2FVerify = "/mfa/init/u2f/verify"
EndpointU2FVerification = "/mfa/u2f/verify"
EndpointMailVerification = "/mail/verification"
EndpointMailVerified = "/mail/verified"
EndpointRegisterOption = "/register/option"
EndpointRegister = "/register"
EndpointExternalRegister = "/register/externalidp"
EndpointExternalRegisterCallback = "/register/externalidp/callback"
EndpointRegisterOrg = "/register/org"
EndpointLogoutDone = "/logout/done"
EndpointLoginSuccess = "/login/success"
EndpointExternalNotFoundOption = "/externaluser/option"
EndpointRoot = "/"
EndpointHealthz = "/healthz"
EndpointReadiness = "/ready"
EndpointLogin = "/login"
EndpointExternalLogin = "/login/externalidp"
EndpointExternalLoginCallback = "/login/externalidp/callback"
EndpointExternalLoginCallbackFormPost = "/login/externalidp/callback/form"
EndpointJWTAuthorize = "/login/jwt/authorize"
EndpointJWTCallback = "/login/jwt/callback"
EndpointLDAPLogin = "/login/ldap"
EndpointLDAPCallback = "/login/ldap/callback"
EndpointPasswordlessLogin = "/login/passwordless"
EndpointPasswordlessRegistration = "/login/passwordless/init"
EndpointPasswordlessPrompt = "/login/passwordless/prompt"
EndpointLoginName = "/loginname"
EndpointUserSelection = "/userselection"
EndpointChangeUsername = "/username/change"
EndpointPassword = "/password"
EndpointInitPassword = "/password/init"
EndpointChangePassword = "/password/change"
EndpointPasswordReset = "/password/reset"
EndpointInitUser = "/user/init"
EndpointMFAVerify = "/mfa/verify"
EndpointMFAPrompt = "/mfa/prompt"
EndpointMFAInitVerify = "/mfa/init/verify"
EndpointMFASMSInitVerify = "/mfa/init/sms/verify"
EndpointMFAOTPVerify = "/mfa/otp/verify"
EndpointMFAInitU2FVerify = "/mfa/init/u2f/verify"
EndpointU2FVerification = "/mfa/u2f/verify"
EndpointMailVerification = "/mail/verification"
EndpointMailVerified = "/mail/verified"
EndpointRegisterOption = "/register/option"
EndpointRegister = "/register"
EndpointExternalRegister = "/register/externalidp"
EndpointExternalRegisterCallback = "/register/externalidp/callback"
EndpointRegisterOrg = "/register/org"
EndpointLogoutDone = "/logout/done"
EndpointLoginSuccess = "/login/success"
EndpointExternalNotFoundOption = "/externaluser/option"
EndpointResources = "/resources"
EndpointDynamicResources = "/resources/dynamic"
@@ -71,6 +72,7 @@ func CreateRouter(login *Login, staticDir http.FileSystem, interceptors ...mux.M
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(EndpointExternalLoginCallbackFormPost, login.handleExternalLoginCallbackForm).Methods(http.MethodPost)
router.HandleFunc(EndpointJWTAuthorize, login.handleJWTRequest).Methods(http.MethodGet)
router.HandleFunc(EndpointJWTCallback, login.handleJWTCallback).Methods(http.MethodGet)
router.HandleFunc(EndpointPasswordlessLogin, login.handlePasswordlessVerification).Methods(http.MethodPost)

View File

@@ -360,6 +360,7 @@ Footer:
PrivacyPolicy: Политика за поверителност
Help: Помогне
SupportEmail: Поддръжка на имейл
SignIn: Влезте с {{.Provider}}
Errors:
Internal: Възникна вътрешна грешка
AuthRequest:

View File

@@ -372,6 +372,8 @@ Footer:
Help: Hilfe
SupportEmail: Support E-Mail
SignIn: Mit {{.Provider}} anmelden
Errors:
Internal: Es ist ein interner Fehler aufgetreten
AuthRequest:

View File

@@ -372,6 +372,8 @@ Footer:
Help: Help
SupportEmail: Support E-mail
SignIn: Sign in with {{.Provider}}
Errors:
Internal: An internal error occurred
AuthRequest:

View File

@@ -354,6 +354,8 @@ Footer:
Help: Ayuda
SupportEmail: Email de soporte
SignIn: Iniciar sesión con {{.Provider}}
Errors:
Internal: Se produjo un error interno
AuthRequest:

View File

@@ -372,6 +372,8 @@ Footer:
Help: Aide
SupportEmail: E-mail d'assistance
SignIn: Connexion avec {{.Provider}}
Errors:
Internal: Une erreur interne s'est produite
AuthRequest:

View File

@@ -372,6 +372,8 @@ Footer:
Help: Aiuto
SupportEmail: E-mail di supporto
SignIn: Accedi con {{.Provider}}
Errors:
Internal: Si è verificato un errore interno
AuthRequest:

View File

@@ -363,6 +363,8 @@ Footer:
PrivacyPolicy: プライバシーポリシー
Help: ヘルプ
SignIn: '{{.Provider}} でサインイン'
Errors:
Internal: 内部でエラーが発生しました
AuthRequest:

View File

@@ -372,6 +372,8 @@ Footer:
Help: Помош
SupportEmail: Е-пошта за поддршка
SignIn: Пријавете се со {{.Provider}}
Errors:
Internal: Се појави внатрешна грешка
AuthRequest:

View File

@@ -372,6 +372,8 @@ Footer:
Help: Pomoc
SupportEmail: E-mail wsparcia
SignIn: Zaloguj się, używając konta {{.Provider}}
Errors:
Internal: Wewnętrzny błąd
AuthRequest:

View File

@@ -366,6 +366,8 @@ Footer:
Help: Ajuda
SupportEmail: E-mail de suporte
SignIn: Iniciar sessão com a {{.Provider}}
Errors:
Internal: Ocorreu um erro interno
AuthRequest:

View File

@@ -372,6 +372,8 @@ Footer:
Help: 帮助
SupportEmail: 支持邮箱
SignIn: 通过 {{.Provider}} 登录
Errors:
Internal: 发生了内部错误
AuthRequest:

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="56px" height="56px" viewBox="18 15.5 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 61 (89581) - https://sketch.com -->
<title>White Logo Square </title>
<desc>Created with Sketch.</desc>
<g id="White-Logo-Square-" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle" fill="" x="6" y="6" width="44" height="44"></rect>
<path d="M28.2226562,20.3846154 C29.0546875,20.3846154 30.0976562,19.8048315 30.71875,19.0317864 C31.28125,18.3312142 31.6914062,17.352829 31.6914062,16.3744437 C31.6914062,16.2415766 31.6796875,16.1087095 31.65625,16 C30.7304687,16.0362365 29.6171875,16.640178 28.9492187,17.4494596 C28.421875,18.06548 27.9414062,19.0317864 27.9414062,20.0222505 C27.9414062,20.1671964 27.9648438,20.3121424 27.9765625,20.3604577 C28.0351562,20.3725366 28.1289062,20.3846154 28.2226562,20.3846154 Z M25.2929688,35 C26.4296875,35 26.9335938,34.214876 28.3515625,34.214876 C29.7929688,34.214876 30.109375,34.9758423 31.375,34.9758423 C32.6171875,34.9758423 33.4492188,33.792117 34.234375,32.6325493 C35.1132812,31.3038779 35.4765625,29.9993643 35.5,29.9389701 C35.4179688,29.9148125 33.0390625,28.9122695 33.0390625,26.0979021 C33.0390625,23.6579784 34.9140625,22.5588048 35.0195312,22.474253 C33.7773438,20.6382708 31.890625,20.5899555 31.375,20.5899555 C29.9804688,20.5899555 28.84375,21.4596313 28.1289062,21.4596313 C27.3554688,21.4596313 26.3359375,20.6382708 25.1289062,20.6382708 C22.8320312,20.6382708 20.5,22.5950413 20.5,26.2911634 C20.5,28.5861411 21.3671875,31.013986 22.4335938,32.5842339 C23.3476562,33.9129053 24.1445312,35 25.2929688,35 Z" id="" fill="#FFFFFF" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="56px" height="56px" viewBox="18 15.5 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 61 (89581) - https://sketch.com -->
<title>Black Logo Square</title>
<desc>Created with Sketch.</desc>
<g id="Black-Logo-Square" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle" fill="" x="6" y="6" width="44" height="44"></rect>
<path d="M28.2226562,20.3846154 C29.0546875,20.3846154 30.0976562,19.8048315 30.71875,19.0317864 C31.28125,18.3312142 31.6914062,17.352829 31.6914062,16.3744437 C31.6914062,16.2415766 31.6796875,16.1087095 31.65625,16 C30.7304687,16.0362365 29.6171875,16.640178 28.9492187,17.4494596 C28.421875,18.06548 27.9414062,19.0317864 27.9414062,20.0222505 C27.9414062,20.1671964 27.9648438,20.3121424 27.9765625,20.3604577 C28.0351562,20.3725366 28.1289062,20.3846154 28.2226562,20.3846154 Z M25.2929688,35 C26.4296875,35 26.9335938,34.214876 28.3515625,34.214876 C29.7929688,34.214876 30.109375,34.9758423 31.375,34.9758423 C32.6171875,34.9758423 33.4492188,33.792117 34.234375,32.6325493 C35.1132812,31.3038779 35.4765625,29.9993643 35.5,29.9389701 C35.4179688,29.9148125 33.0390625,28.9122695 33.0390625,26.0979021 C33.0390625,23.6579784 34.9140625,22.5588048 35.0195312,22.474253 C33.7773438,20.6382708 31.890625,20.5899555 31.375,20.5899555 C29.9804688,20.5899555 28.84375,21.4596313 28.1289062,21.4596313 C27.3554688,21.4596313 26.3359375,20.6382708 25.1289062,20.6382708 C22.8320312,20.6382708 20.5,22.5950413 20.5,26.2911634 C20.5,28.5861411 21.3671875,31.013986 22.4335938,32.5842339 C23.3476562,33.9129053 24.1445312,35 25.2929688,35 Z" id="" fill="#000000" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -2,6 +2,7 @@ $lgn-idp-margin: 0.5rem 0;
$lgn-idp-padding: 0 1px;
$lgn-idp-provider-name-line-height: 36px;
$lgn-idp-border-radius: 0.5rem;
$lgn-idp-logo-size: 46px;
@mixin lgn-idp-base {
display: block;
@@ -17,14 +18,14 @@ $lgn-idp-border-radius: 0.5rem;
transition: border-color 0.2s ease-in-out;
span.logo {
height: 46px;
width: 46px;
height: $lgn-idp-logo-size;
width: $lgn-idp-logo-size;
}
span.provider-name {
line-height: $lgn-idp-provider-name-line-height;
position: absolute;
left: 50%;
position: relative;
left: calc(50% - $lgn-idp-logo-size);
transform: translateX(-50%);
}
@@ -75,4 +76,17 @@ $lgn-idp-border-radius: 0.5rem;
border-radius: 5px;
}
}
&.apple {
span.logo {
height: 46px;
width: 46px;
background-image: var(--apple-image-src);
background-size: 25px;
background-position: center;
background-repeat: no-repeat;
border-radius: 5px;
transform: translateY(-2px);
}
}
}

View File

@@ -117,6 +117,8 @@
--zitadel-color-github-background: #ffffff;
--zitadel-color-gitlab-text: #8b8d8d;
--zitadel-color-gitlab-background: #ffffff;
--zitadel-color-apple-text: #8b8d8d;
--zitadel-color-apple-background: #ffffff;
--zitadel-color-qr: var(--zitadel-color-black);
--zitadel-color-qr-background: var(--zitadel-color-white);
@@ -125,6 +127,7 @@
--github-image-src: url(../../../images/idp/github.png);
--gitlab-image-src: url(../../../images/idp/gitlab.png);
--azure-image-src: url(../../../images/idp/ms.svg);
--apple-image-src: url(../../../images/idp/apple.svg);
}
.lgn-dark-theme {
@@ -227,9 +230,12 @@
--zitadel-color-github-background: #ffffff;
--zitadel-color-gitlab-text: #8b8d8d;
--zitadel-color-gitlab-background: #ffffff;
--zitadel-color-apple-text: #8b8d8d;
--zitadel-color-apple-background: #ffffff;
--google-image-src: url(../../../images/idp/google.png);
--github-image-src: url(../../../images/idp/github-white.png);
--gitlab-image-src: url(../../../images/idp/gitlab.png);
--azure-image-src: url(../../../images/idp/ms.svg);
--apple-image-src: url(../../../images/idp/apple-dark.svg);
}

View File

@@ -52,7 +52,11 @@
<a href="{{ externalIDPAuthURL $reqid $provider.IDPConfigID}}"
class="lgn-idp {{idpProviderClass $provider.IDPType}}">
<span class="logo"></span>
{{if $provider.IDPType.IsSignInButton}}
<span class="provider-name">{{t "SignIn" "Provider" $provider.DisplayName}}</span>
{{else}}
<span class="provider-name">{{$provider.DisplayName}}</span>
{{end}}
</a>
{{end}}
</div>

View File

@@ -29,7 +29,11 @@
<a href="{{ externalIDPRegisterURL $reqid $provider.IDPConfigID}}"
class="lgn-idp {{idpProviderClass $provider.IDPType}}">
<span class="logo"></span>
{{if $provider.IDPType.IsSignInButton}}
<span class="provider-name">{{t "SignIn" "Provider" $provider.DisplayName}}</span>
{{else}}
<span class="provider-name">{{$provider.DisplayName}}</span>
{{end}}
</a>
{{end}}
{{end}}