mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-12 11:04:25 +00:00
Merge branch 'main' into grcp-server-reflect
This commit is contained in:
commit
383e68b819
@ -7,6 +7,7 @@
|
||||
/k8s/
|
||||
/node_modules/
|
||||
/console/src/app/proto/generated/
|
||||
/console/.angular
|
||||
/console/tmp/
|
||||
.releaserc.js
|
||||
changelog.config.js
|
||||
@ -18,3 +19,4 @@ pkg/grpc/*/*.pb.*
|
||||
pkg/grpc/*/*.swagger.json
|
||||
.goreleaser.yaml
|
||||
.artifacts/
|
||||
.vscode
|
||||
|
2
.github/workflows/zitadel.yml
vendored
2
.github/workflows/zitadel.yml
vendored
@ -80,7 +80,7 @@ jobs:
|
||||
name: go-codecov
|
||||
- name: Bump Chart Version
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
if: steps.semantic.outputs.new_release_published == 'true' && github.ref == 'refs/heads/main'
|
||||
if: steps.semantic.outputs.new_release_published == 'true' && github.ref == 'refs/heads/next'
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
repository: zitadel/zitadel-charts
|
||||
|
@ -338,6 +338,7 @@ Please refer to the [README](./docs/README.md) for more information and local te
|
||||
|
||||
- **Code with variables**: Make sure that code snippets can be used by setting environment variables, instead of manually replacing a placeholder.
|
||||
- **Embedded files**: When embedding mdx files, make sure the template ist prefixed by "_" (lowdash). The content will be rendered inside the parent page, but is not accessible individually (eg, by search).
|
||||
- **Don't repeat yourself**: When using the same content in multiple places, save and manage the content as separate file and make use of embedded files to import it into other docs pages.
|
||||
- **Embedded code**: You can embed code snippets from a repository. See the [plugin](https://github.com/saucelabs/docusaurus-theme-github-codeblock#usage) for usage.
|
||||
|
||||
### Docs Pull Request
|
||||
|
@ -96,4 +96,16 @@ protoc \
|
||||
--validate_out=lang=go:${GOPATH}/src \
|
||||
${PROTO_PATH}/session/v2alpha/session_service.proto
|
||||
|
||||
protoc \
|
||||
-I=/proto/include \
|
||||
--grpc-gateway_out ${GOPATH}/src \
|
||||
--grpc-gateway_opt logtostderr=true \
|
||||
--grpc-gateway_opt allow_delete_body=true \
|
||||
--openapiv2_out ${OPENAPI_PATH} \
|
||||
--openapiv2_opt logtostderr=true \
|
||||
--openapiv2_opt allow_delete_body=true \
|
||||
--zitadel_out=${GOPATH}/src \
|
||||
--validate_out=lang=go:${GOPATH}/src \
|
||||
${PROTO_PATH}/settings/v2alpha/settings_service.proto
|
||||
|
||||
echo "done generating grpc"
|
||||
|
@ -267,6 +267,7 @@ Console:
|
||||
LongCache:
|
||||
MaxAge: 12h
|
||||
SharedMaxAge: 168h #7d
|
||||
InstanceManagementURL: ""
|
||||
|
||||
Notification:
|
||||
Repository:
|
||||
|
@ -13,11 +13,11 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed 10_create_temp_table.sql
|
||||
//go:embed 10/10_create_temp_table.sql
|
||||
correctCreationDate10CreateTable string
|
||||
//go:embed 10_fill_table.sql
|
||||
//go:embed 10/10_fill_table.sql
|
||||
correctCreationDate10FillTable string
|
||||
//go:embed 10_update.sql
|
||||
//go:embed 10/10_update.sql
|
||||
correctCreationDate10Update string
|
||||
)
|
||||
|
||||
|
32
cmd/setup/11.go
Normal file
32
cmd/setup/11.go
Normal file
@ -0,0 +1,32 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed 11.sql
|
||||
addEventCreatedAt string
|
||||
)
|
||||
|
||||
type AddEventCreatedAt struct {
|
||||
step10 *CorrectCreationDate
|
||||
dbClient *database.DB
|
||||
}
|
||||
|
||||
func (mig *AddEventCreatedAt) Execute(ctx context.Context) error {
|
||||
// execute step 10 again because events created after the first execution of step 10
|
||||
// could still have the wrong ordering of sequences and creation date
|
||||
if err := mig.step10.Execute(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := mig.dbClient.ExecContext(ctx, addEventCreatedAt)
|
||||
return err
|
||||
}
|
||||
|
||||
func (mig *AddEventCreatedAt) String() string {
|
||||
return "11_event_created_at"
|
||||
}
|
15
cmd/setup/11.sql
Normal file
15
cmd/setup/11.sql
Normal file
@ -0,0 +1,15 @@
|
||||
BEGIN;
|
||||
-- create table with empty created_at
|
||||
ALTER TABLE eventstore.events ADD COLUMN created_at TIMESTAMPTZ DEFAULT NULL;
|
||||
COMMIT;
|
||||
|
||||
BEGIN;
|
||||
-- backfill created_at
|
||||
UPDATE eventstore.events SET created_at = creation_date WHERE created_at IS NULL;
|
||||
COMMIT;
|
||||
|
||||
BEGIN;
|
||||
-- set column rules
|
||||
ALTER TABLE eventstore.events ALTER COLUMN created_at SET DEFAULT clock_timestamp();
|
||||
ALTER TABLE eventstore.events ALTER COLUMN created_at SET NOT NULL;
|
||||
COMMIT;
|
@ -66,6 +66,7 @@ type Steps struct {
|
||||
s8AuthTokens *AuthTokenIndexes
|
||||
s9EventstoreIndexes2 *EventstoreIndexesNew
|
||||
CorrectCreationDate *CorrectCreationDate
|
||||
s11AddEventCreatedAt *AddEventCreatedAt
|
||||
}
|
||||
|
||||
type encryptionKeyConfig struct {
|
||||
|
@ -91,6 +91,7 @@ func Setup(config *Config, steps *Steps, masterKey string) {
|
||||
steps.s8AuthTokens = &AuthTokenIndexes{dbClient: dbClient}
|
||||
steps.s9EventstoreIndexes2 = New09(dbClient)
|
||||
steps.CorrectCreationDate.dbClient = dbClient
|
||||
steps.s11AddEventCreatedAt = &AddEventCreatedAt{dbClient: dbClient, step10: steps.CorrectCreationDate}
|
||||
|
||||
err = projection.Create(ctx, dbClient, eventstoreClient, config.Projections, nil, nil)
|
||||
logging.OnError(err).Fatal("unable to start projections")
|
||||
@ -128,6 +129,8 @@ func Setup(config *Config, steps *Steps, masterKey string) {
|
||||
logging.OnError(err).Fatal("unable to migrate step 9")
|
||||
err = migration.Migrate(ctx, eventstoreClient, steps.CorrectCreationDate)
|
||||
logging.OnError(err).Fatal("unable to migrate step 10")
|
||||
err = migration.Migrate(ctx, eventstoreClient, steps.s11AddEventCreatedAt)
|
||||
logging.OnError(err).Fatal("unable to migrate step 11")
|
||||
|
||||
for _, repeatableStep := range repeatableSteps {
|
||||
err = migration.Migrate(ctx, eventstoreClient, repeatableStep)
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"crypto/tls"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"net"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@ -21,7 +21,6 @@ import (
|
||||
"github.com/zitadel/saml/pkg/provider"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/zitadel/zitadel/cmd/key"
|
||||
cmd_tls "github.com/zitadel/zitadel/cmd/tls"
|
||||
@ -34,6 +33,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/auth"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/management"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/session/v2"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/settings/v2"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/system"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/user/v2"
|
||||
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||
@ -301,8 +301,13 @@ func startAPIs(
|
||||
if accessSvc.Enabled() {
|
||||
logging.Warn("access logs are currently in beta")
|
||||
}
|
||||
accessInterceptor := middleware.NewAccessInterceptor(accessSvc, config.Quotas.Access)
|
||||
apis, err := api.New(ctx, config.Port, router, queries, verifier, config.InternalAuthZ, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader, accessSvc)
|
||||
exhaustedCookieHandler := http_util.NewCookieHandler(
|
||||
http_util.WithUnsecure(),
|
||||
http_util.WithNonHttpOnly(),
|
||||
http_util.WithMaxAge(int(math.Floor(config.Quotas.Access.ExhaustedCookieMaxAge.Seconds()))),
|
||||
)
|
||||
limitingAccessInterceptor := middleware.NewAccessInterceptor(accessSvc, exhaustedCookieHandler, config.Quotas.Access)
|
||||
apis, err := api.New(ctx, config.Port, router, queries, verifier, config.InternalAuthZ, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader, limitingAccessInterceptor)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating api %w", err)
|
||||
}
|
||||
@ -332,9 +337,12 @@ func startAPIs(
|
||||
if err := apis.RegisterService(ctx, session.CreateServer(commands, queries, permissionCheck)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, settings.CreateServer(commands, queries, config.ExternalSecure)); err != nil {
|
||||
return err
|
||||
}
|
||||
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader, login.IgnoreInstanceEndpoints...)
|
||||
assetsCache := middleware.AssetsCacheInterceptor(config.AssetStorage.Cache.MaxAge, config.AssetStorage.Cache.SharedMaxAge)
|
||||
apis.RegisterHandlerOnPrefix(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator(), store, queries, middleware.CallDurationHandler, instanceInterceptor.Handler, assetsCache.Handler, accessInterceptor.Handle))
|
||||
apis.RegisterHandlerOnPrefix(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator(), store, queries, middleware.CallDurationHandler, instanceInterceptor.Handler, assetsCache.Handler, limitingAccessInterceptor.Handle))
|
||||
|
||||
userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, id.SonyFlakeGenerator(), config.ExternalSecure, login.EndpointResources)
|
||||
if err != nil {
|
||||
@ -355,25 +363,25 @@ func startAPIs(
|
||||
}
|
||||
apis.RegisterHandlerOnPrefix(openapi.HandlerPrefix, openAPIHandler)
|
||||
|
||||
oidcProvider, err := oidc.NewProvider(config.OIDC, login.DefaultLoggedOutPath, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.OIDCKey, eventstore, dbClient, userAgentInterceptor, instanceInterceptor.Handler, accessInterceptor.Handle)
|
||||
oidcProvider, err := oidc.NewProvider(config.OIDC, login.DefaultLoggedOutPath, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.OIDCKey, eventstore, dbClient, userAgentInterceptor, instanceInterceptor.Handler, limitingAccessInterceptor.Handle)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start oidc provider: %w", err)
|
||||
}
|
||||
apis.RegisterHandlerPrefixes(oidcProvider.HttpHandler(), "/.well-known/openid-configuration", "/oidc/v1", "/oauth/v2")
|
||||
|
||||
samlProvider, err := saml.NewProvider(config.SAML, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.SAML, eventstore, dbClient, instanceInterceptor.Handler, userAgentInterceptor, accessInterceptor.Handle)
|
||||
samlProvider, err := saml.NewProvider(config.SAML, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.SAML, eventstore, dbClient, instanceInterceptor.Handler, userAgentInterceptor, limitingAccessInterceptor.Handle)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start saml provider: %w", err)
|
||||
}
|
||||
apis.RegisterHandlerOnPrefix(saml.HandlerPrefix, samlProvider.HttpHandler())
|
||||
|
||||
c, err := console.Start(config.Console, config.ExternalSecure, oidcProvider.IssuerFromRequest, middleware.CallDurationHandler, instanceInterceptor.Handler, accessInterceptor.Handle, config.CustomerPortal)
|
||||
c, err := console.Start(config.Console, config.ExternalSecure, oidcProvider.IssuerFromRequest, middleware.CallDurationHandler, instanceInterceptor.Handler, limitingAccessInterceptor, config.CustomerPortal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start console: %w", err)
|
||||
}
|
||||
apis.RegisterHandlerOnPrefix(console.HandlerPrefix, c)
|
||||
|
||||
l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, console.HandlerPrefix+"/", op.AuthCallbackURL(oidcProvider), provider.AuthCallbackURL(samlProvider), config.ExternalSecure, userAgentInterceptor, op.NewIssuerInterceptor(oidcProvider.IssuerFromRequest).Handler, provider.NewIssuerInterceptor(samlProvider.IssuerFromRequest).Handler, instanceInterceptor.Handler, assetsCache.Handler, accessInterceptor.Handle, keys.User, keys.IDPConfig, keys.CSRFCookieKey)
|
||||
l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, console.HandlerPrefix+"/", op.AuthCallbackURL(oidcProvider), provider.AuthCallbackURL(samlProvider), config.ExternalSecure, userAgentInterceptor, op.NewIssuerInterceptor(oidcProvider.IssuerFromRequest).Handler, provider.NewIssuerInterceptor(samlProvider.IssuerFromRequest).Handler, instanceInterceptor.Handler, assetsCache.Handler, limitingAccessInterceptor.Handle, keys.User, keys.IDPConfig, keys.CSRFCookieKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to start login: %w", err)
|
||||
}
|
||||
@ -385,20 +393,11 @@ func startAPIs(
|
||||
return nil
|
||||
}
|
||||
|
||||
func reusePort(network, address string, conn syscall.RawConn) error {
|
||||
return conn.Control(func(descriptor uintptr) {
|
||||
err := syscall.SetsockoptInt(int(descriptor), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func listen(ctx context.Context, router *mux.Router, port uint16, tlsConfig *tls.Config, shutdown <-chan os.Signal) error {
|
||||
http2Server := &http2.Server{}
|
||||
http1Server := &http.Server{Handler: h2c.NewHandler(router, http2Server), TLSConfig: tlsConfig}
|
||||
|
||||
lc := &net.ListenConfig{Control: reusePort}
|
||||
lc := listenConfig()
|
||||
lis, err := lc.Listen(ctx, "tcp", fmt.Sprintf(":%d", port))
|
||||
if err != nil {
|
||||
return fmt.Errorf("tcp listener on %d failed: %w", port, err)
|
||||
|
11
cmd/start/start_port.go
Normal file
11
cmd/start/start_port.go
Normal file
@ -0,0 +1,11 @@
|
||||
//go:build !integration
|
||||
|
||||
package start
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func listenConfig() *net.ListenConfig {
|
||||
return &net.ListenConfig{}
|
||||
}
|
25
cmd/start/start_port_integration.go
Normal file
25
cmd/start/start_port_integration.go
Normal file
@ -0,0 +1,25 @@
|
||||
//go:build integration
|
||||
|
||||
package start
|
||||
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func listenConfig() *net.ListenConfig {
|
||||
return &net.ListenConfig{
|
||||
Control: reusePort,
|
||||
}
|
||||
}
|
||||
|
||||
func reusePort(network, address string, conn syscall.RawConn) error {
|
||||
return conn.Control(func(descriptor uintptr) {
|
||||
err := syscall.SetsockoptInt(int(descriptor), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
}
|
@ -30,19 +30,23 @@
|
||||
<span class="fill-space"></span>
|
||||
<div class="app-specs cnsl-secondary-text">
|
||||
<div class="row" *ngIf="isOIDC && method && method.responseType !== undefined">
|
||||
<span>{{ 'APP.OIDC.RESPONSETYPE' | translate }}</span>
|
||||
<span class="row-entry">{{ 'APP.OIDC.RESPONSETYPE' | translate }}</span>
|
||||
<span>{{ 'APP.OIDC.RESPONSE.' + method.responseType.toString() | translate }}</span>
|
||||
</div>
|
||||
<div class="row" *ngIf="isOIDC && method.grantType !== undefined">
|
||||
<span>{{ 'APP.GRANT' | translate }}</span>
|
||||
<span>{{ 'APP.OIDC.GRANT.' + method.grantType.toString() | translate }}</span>
|
||||
<span class="row-entry">{{ 'APP.GRANT' | translate }}</span>
|
||||
<span
|
||||
><span class="space" *ngFor="let grant of method.grantType">{{
|
||||
'APP.OIDC.GRANT.' + grant.toString() | translate
|
||||
}}</span></span
|
||||
>
|
||||
</div>
|
||||
<div class="row" *ngIf="isOIDC && method.authMethod !== undefined">
|
||||
<span>{{ 'APP.AUTHMETHOD' | translate }}</span>
|
||||
<span class="row-entry">{{ 'APP.AUTHMETHOD' | translate }}</span>
|
||||
<span>{{ 'APP.OIDC.AUTHMETHOD.' + method.authMethod.toString() | translate }}</span>
|
||||
</div>
|
||||
<div class="row" *ngIf="!isOIDC && method.apiAuthMethod !== undefined">
|
||||
<span>{{ 'APP.AUTHMETHOD' | translate }}</span>
|
||||
<span class="row-entry">{{ 'APP.AUTHMETHOD' | translate }}</span>
|
||||
<span>{{ 'APP.API.AUTHMETHOD.' + method.apiAuthMethod.toString() | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -155,7 +155,11 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:first-child {
|
||||
.space {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.row-entry {
|
||||
margin-right: 1rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -14,7 +14,7 @@ export interface RadioItemAuthType {
|
||||
prefix: string;
|
||||
background: string;
|
||||
responseType?: OIDCResponseType;
|
||||
grantType?: OIDCGrantType;
|
||||
grantType?: OIDCGrantType[];
|
||||
authMethod?: OIDCAuthMethodType;
|
||||
apiAuthMethod?: APIAuthMethodType;
|
||||
recommended?: boolean;
|
||||
|
@ -58,13 +58,9 @@
|
||||
</form>
|
||||
</mat-step>
|
||||
|
||||
<!-- skip for native OIDC and SAML applications -->
|
||||
<!-- skip for SAML applications -->
|
||||
<mat-step
|
||||
*ngIf="
|
||||
(appType?.value?.createType === AppCreateType.OIDC &&
|
||||
appType?.value.oidcAppType !== OIDCAppType.OIDC_APP_TYPE_NATIVE) ||
|
||||
appType?.value?.createType === AppCreateType.API
|
||||
"
|
||||
*ngIf="appType?.value?.createType === AppCreateType.OIDC || appType?.value?.createType === AppCreateType.API"
|
||||
[stepControl]="secondFormGroup"
|
||||
[editable]="true"
|
||||
>
|
||||
@ -93,9 +89,11 @@
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
|
||||
<!-- show redirect step only for OIDC apps -->
|
||||
<mat-step *ngIf="appType?.value?.createType === AppCreateType.OIDC" [editable]="true">
|
||||
<mat-step
|
||||
*ngIf="appType?.value?.createType === AppCreateType.OIDC && authMethod?.value !== 'DEVICECODE'"
|
||||
[editable]="true"
|
||||
>
|
||||
<ng-template matStepLabel>{{ 'APP.OIDC.REDIRECTSECTION' | translate }}</ng-template>
|
||||
|
||||
<p class="step-title">{{ 'APP.OIDC.REDIRECTTITLE' | translate }}</p>
|
||||
@ -431,7 +429,13 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="content" *ngIf="formappType?.value?.createType === AppCreateType.OIDC">
|
||||
<div
|
||||
class="content"
|
||||
*ngIf="
|
||||
formappType?.value?.createType === AppCreateType.OIDC &&
|
||||
!(oidcAppRequest.toObject().appType === OIDCAppType.OIDC_APP_TYPE_NATIVE && grantTypesListContainsOnlyDeviceCode)
|
||||
"
|
||||
>
|
||||
<div class="formfield full-width">
|
||||
<cnsl-redirect-uris
|
||||
class="redirect-section"
|
||||
|
@ -32,6 +32,7 @@ import { AppSecretDialogComponent } from '../app-secret-dialog/app-secret-dialog
|
||||
import {
|
||||
BASIC_AUTH_METHOD,
|
||||
CODE_METHOD,
|
||||
DEVICE_CODE_METHOD,
|
||||
getPartialConfigFromAuthMethod,
|
||||
IMPLICIT_METHOD,
|
||||
PKCE_METHOD,
|
||||
@ -112,6 +113,7 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
{ type: OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE, checked: true, disabled: false },
|
||||
{ type: OIDCGrantType.OIDC_GRANT_TYPE_IMPLICIT, checked: false, disabled: true },
|
||||
{ type: OIDCGrantType.OIDC_GRANT_TYPE_REFRESH_TOKEN, checked: false, disabled: true },
|
||||
{ type: OIDCGrantType.OIDC_GRANT_TYPE_DEVICE_CODE, checked: false, disabled: true },
|
||||
];
|
||||
|
||||
public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE];
|
||||
@ -163,7 +165,7 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
|
||||
switch (this.appType?.value.oidcAppType) {
|
||||
case OIDCAppType.OIDC_APP_TYPE_NATIVE:
|
||||
this.authMethods = [PKCE_METHOD];
|
||||
this.authMethods = [PKCE_METHOD, DEVICE_CODE_METHOD];
|
||||
|
||||
// automatically set to PKCE and skip step
|
||||
this.oidcAppRequest.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE]);
|
||||
@ -473,6 +475,13 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
return this.form.get('grantTypesList');
|
||||
}
|
||||
|
||||
get grantTypesListContainsOnlyDeviceCode(): boolean {
|
||||
return (
|
||||
this.oidcAppRequest.toObject().grantTypesList.length === 1 &&
|
||||
this.oidcAppRequest.toObject().grantTypesList[0] === OIDCGrantType.OIDC_GRANT_TYPE_DEVICE_CODE
|
||||
);
|
||||
}
|
||||
|
||||
get formappType(): AbstractControl | null {
|
||||
return this.form.get('appType');
|
||||
}
|
||||
@ -480,9 +489,6 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
||||
get formMetadataUrl(): AbstractControl | null {
|
||||
return this.form.get('metadataUrl');
|
||||
}
|
||||
// get formapplicationType(): AbstractControl | null {
|
||||
// return this.form.get('applicationType');
|
||||
// }
|
||||
|
||||
get authMethodType(): AbstractControl | null {
|
||||
return this.form.get('authMethodType');
|
||||
|
@ -46,6 +46,7 @@ import {
|
||||
BASIC_AUTH_METHOD,
|
||||
CODE_METHOD,
|
||||
CUSTOM_METHOD,
|
||||
DEVICE_CODE_METHOD,
|
||||
getAuthMethodFromPartialConfig,
|
||||
getPartialConfigFromAuthMethod,
|
||||
IMPLICIT_METHOD,
|
||||
@ -89,6 +90,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
||||
public oidcGrantTypes: OIDCGrantType[] = [
|
||||
OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE,
|
||||
OIDCGrantType.OIDC_GRANT_TYPE_IMPLICIT,
|
||||
OIDCGrantType.OIDC_GRANT_TYPE_DEVICE_CODE,
|
||||
OIDCGrantType.OIDC_GRANT_TYPE_REFRESH_TOKEN,
|
||||
];
|
||||
public oidcAppTypes: OIDCAppType[] = [
|
||||
@ -274,6 +276,16 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
||||
if (this.app.oidcConfig) {
|
||||
this.getAuthMethodOptions('OIDC');
|
||||
|
||||
if (
|
||||
this.app.oidcConfig.grantTypesList.length === 1 &&
|
||||
this.app.oidcConfig.grantTypesList[0] === OIDCGrantType.OIDC_GRANT_TYPE_DEVICE_CODE
|
||||
) {
|
||||
this.settingsList = [
|
||||
{ id: 'configuration', i18nKey: 'APP.CONFIGURATION' },
|
||||
{ id: 'token', i18nKey: 'APP.TOKEN' },
|
||||
{ id: 'urls', i18nKey: 'APP.URLS' },
|
||||
];
|
||||
} else {
|
||||
this.settingsList = [
|
||||
{ id: 'configuration', i18nKey: 'APP.CONFIGURATION' },
|
||||
{ id: 'token', i18nKey: 'APP.TOKEN' },
|
||||
@ -281,6 +293,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
||||
{ id: 'additional-origins', i18nKey: 'APP.ADDITIONALORIGINS' },
|
||||
{ id: 'urls', i18nKey: 'APP.URLS' },
|
||||
];
|
||||
}
|
||||
|
||||
this.initialAuthMethod = this.authMethodFromPartialConfig({ oidc: this.app.oidcConfig });
|
||||
this.currentAuthMethod = this.initialAuthMethod;
|
||||
@ -381,7 +394,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
||||
if (type === 'OIDC') {
|
||||
switch (this.app?.oidcConfig?.appType) {
|
||||
case OIDCAppType.OIDC_APP_TYPE_NATIVE:
|
||||
this.authMethods = [PKCE_METHOD, CUSTOM_METHOD];
|
||||
this.authMethods = [PKCE_METHOD, DEVICE_CODE_METHOD, CUSTOM_METHOD];
|
||||
break;
|
||||
case OIDCAppType.OIDC_APP_TYPE_WEB:
|
||||
this.authMethods = [PKCE_METHOD, CODE_METHOD, PK_JWT_METHOD, POST_METHOD];
|
||||
|
@ -16,10 +16,11 @@ export const CODE_METHOD: RadioItemAuthType = {
|
||||
prefix: 'CODE',
|
||||
background: 'linear-gradient(40deg, rgb(25 105 143) 30%, rgb(23 95 129))',
|
||||
responseType: OIDCResponseType.OIDC_RESPONSE_TYPE_CODE,
|
||||
grantType: OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE,
|
||||
grantType: [OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE],
|
||||
authMethod: OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_BASIC,
|
||||
recommended: false,
|
||||
};
|
||||
|
||||
export const PKCE_METHOD: RadioItemAuthType = {
|
||||
key: 'PKCE',
|
||||
titleI18nKey: 'APP.AUTHMETHODS.PKCE.TITLE',
|
||||
@ -28,10 +29,11 @@ export const PKCE_METHOD: RadioItemAuthType = {
|
||||
prefix: 'PKCE',
|
||||
background: 'linear-gradient(40deg, #059669 30%, #047857)',
|
||||
responseType: OIDCResponseType.OIDC_RESPONSE_TYPE_CODE,
|
||||
grantType: OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE,
|
||||
grantType: [OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE],
|
||||
authMethod: OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE,
|
||||
recommended: true,
|
||||
};
|
||||
|
||||
export const POST_METHOD: RadioItemAuthType = {
|
||||
key: 'POST',
|
||||
titleI18nKey: 'APP.AUTHMETHODS.POST.TITLE',
|
||||
@ -40,10 +42,11 @@ export const POST_METHOD: RadioItemAuthType = {
|
||||
prefix: 'POST',
|
||||
background: 'linear-gradient(40deg, #c53b3b 30%, rgb(169 51 51))',
|
||||
responseType: OIDCResponseType.OIDC_RESPONSE_TYPE_CODE,
|
||||
grantType: OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE,
|
||||
grantType: [OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE],
|
||||
authMethod: OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_POST,
|
||||
notRecommended: true,
|
||||
};
|
||||
|
||||
export const PK_JWT_METHOD: RadioItemAuthType = {
|
||||
key: 'PK_JWT',
|
||||
titleI18nKey: 'APP.AUTHMETHODS.PK_JWT.TITLE',
|
||||
@ -52,11 +55,12 @@ export const PK_JWT_METHOD: RadioItemAuthType = {
|
||||
prefix: 'JWT',
|
||||
background: 'linear-gradient(40deg, rgb(70 77 145) 30%, rgb(58 65 124))',
|
||||
responseType: OIDCResponseType.OIDC_RESPONSE_TYPE_CODE,
|
||||
grantType: OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE,
|
||||
grantType: [OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE],
|
||||
authMethod: OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT,
|
||||
apiAuthMethod: APIAuthMethodType.API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT,
|
||||
// recommended: true,
|
||||
};
|
||||
|
||||
export const BASIC_AUTH_METHOD: RadioItemAuthType = {
|
||||
key: 'BASIC',
|
||||
titleI18nKey: 'APP.AUTHMETHODS.BASIC.TITLE',
|
||||
@ -65,7 +69,7 @@ export const BASIC_AUTH_METHOD: RadioItemAuthType = {
|
||||
prefix: 'BASIC',
|
||||
background: 'linear-gradient(40deg, #c53b3b 30%, rgb(169 51 51))',
|
||||
responseType: OIDCResponseType.OIDC_RESPONSE_TYPE_CODE,
|
||||
grantType: OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE,
|
||||
grantType: [OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE],
|
||||
authMethod: OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_POST,
|
||||
apiAuthMethod: APIAuthMethodType.API_AUTH_METHOD_TYPE_BASIC,
|
||||
};
|
||||
@ -78,11 +82,24 @@ export const IMPLICIT_METHOD: RadioItemAuthType = {
|
||||
prefix: 'IMP',
|
||||
background: 'linear-gradient(40deg, #c53b3b 30%, rgb(169 51 51))',
|
||||
responseType: OIDCResponseType.OIDC_RESPONSE_TYPE_ID_TOKEN,
|
||||
grantType: OIDCGrantType.OIDC_GRANT_TYPE_IMPLICIT,
|
||||
grantType: [OIDCGrantType.OIDC_GRANT_TYPE_IMPLICIT],
|
||||
authMethod: OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE,
|
||||
notRecommended: true,
|
||||
};
|
||||
|
||||
export const DEVICE_CODE_METHOD: RadioItemAuthType = {
|
||||
key: 'DEVICECODE',
|
||||
titleI18nKey: 'APP.AUTHMETHODS.DEVICECODE.TITLE',
|
||||
descI18nKey: 'APP.AUTHMETHODS.DEVICECODE.DESCRIPTION',
|
||||
disabled: false,
|
||||
prefix: 'DEVICECODE',
|
||||
background: 'linear-gradient(40deg, rgb(56 189 248) 30%, rgb(14 165 233))',
|
||||
responseType: OIDCResponseType.OIDC_RESPONSE_TYPE_CODE,
|
||||
grantType: [OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE, OIDCGrantType.OIDC_GRANT_TYPE_DEVICE_CODE],
|
||||
authMethod: OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_BASIC,
|
||||
recommended: false,
|
||||
};
|
||||
|
||||
export const CUSTOM_METHOD: RadioItemAuthType = {
|
||||
key: 'CUSTOM',
|
||||
titleI18nKey: 'APP.AUTHMETHODS.CUSTOM.TITLE',
|
||||
@ -112,6 +129,15 @@ export function getPartialConfigFromAuthMethod(authMethod: string):
|
||||
},
|
||||
};
|
||||
return config;
|
||||
case DEVICE_CODE_METHOD.key:
|
||||
config = {
|
||||
oidc: {
|
||||
responseTypesList: [OIDCResponseType.OIDC_RESPONSE_TYPE_CODE],
|
||||
grantTypesList: [OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE, OIDCGrantType.OIDC_GRANT_TYPE_DEVICE_CODE],
|
||||
authMethodType: OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE,
|
||||
},
|
||||
};
|
||||
return config;
|
||||
case PKCE_METHOD.key:
|
||||
config = {
|
||||
oidc: {
|
||||
@ -211,6 +237,38 @@ export function getAuthMethodFromPartialConfig(config: {
|
||||
OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_POST,
|
||||
]);
|
||||
|
||||
const deviceCode = JSON.stringify([
|
||||
[OIDCResponseType.OIDC_RESPONSE_TYPE_CODE],
|
||||
[OIDCGrantType.OIDC_GRANT_TYPE_DEVICE_CODE],
|
||||
OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE,
|
||||
]);
|
||||
|
||||
const deviceCodeWithCode = JSON.stringify([
|
||||
[OIDCResponseType.OIDC_RESPONSE_TYPE_CODE],
|
||||
[
|
||||
OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE,
|
||||
OIDCGrantType.OIDC_GRANT_TYPE_DEVICE_CODE,
|
||||
// OIDCGrantType.OIDC_GRANT_TYPE_REFRESH_TOKEN,
|
||||
],
|
||||
OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE,
|
||||
]);
|
||||
|
||||
const deviceCodeWithCodeAndRefresh = JSON.stringify([
|
||||
[OIDCResponseType.OIDC_RESPONSE_TYPE_CODE],
|
||||
[
|
||||
OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE,
|
||||
OIDCGrantType.OIDC_GRANT_TYPE_DEVICE_CODE,
|
||||
OIDCGrantType.OIDC_GRANT_TYPE_REFRESH_TOKEN,
|
||||
],
|
||||
OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE,
|
||||
]);
|
||||
|
||||
const deviceCodeWithRefresh = JSON.stringify([
|
||||
[OIDCResponseType.OIDC_RESPONSE_TYPE_CODE],
|
||||
[OIDCGrantType.OIDC_GRANT_TYPE_DEVICE_CODE, OIDCGrantType.OIDC_GRANT_TYPE_REFRESH_TOKEN],
|
||||
OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE,
|
||||
]);
|
||||
|
||||
const pkjwt = JSON.stringify([
|
||||
[OIDCResponseType.OIDC_RESPONSE_TYPE_CODE],
|
||||
[OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE],
|
||||
@ -245,6 +303,15 @@ export function getAuthMethodFromPartialConfig(config: {
|
||||
case postWithRefresh:
|
||||
return POST_METHOD.key;
|
||||
|
||||
case deviceCode:
|
||||
return DEVICE_CODE_METHOD.key;
|
||||
case deviceCodeWithCode:
|
||||
return DEVICE_CODE_METHOD.key;
|
||||
case deviceCodeWithRefresh:
|
||||
return DEVICE_CODE_METHOD.key;
|
||||
case deviceCodeWithCodeAndRefresh:
|
||||
return DEVICE_CODE_METHOD.key;
|
||||
|
||||
case pkjwt:
|
||||
return PK_JWT_METHOD.key;
|
||||
case pkjwtWithRefresh:
|
||||
|
@ -1965,7 +1965,8 @@
|
||||
"GRANT": {
|
||||
"0": "Authorization Code",
|
||||
"1": "Implicit",
|
||||
"2": "Refresh Token"
|
||||
"2": "Refresh Token",
|
||||
"3": "Device Code"
|
||||
},
|
||||
"AUTHMETHOD": {
|
||||
"0": "Basic",
|
||||
@ -2056,6 +2057,10 @@
|
||||
"TITLE": "Implicit",
|
||||
"DESCRIPTION": "Erhalte die Token direkt vom authorize Endpoint"
|
||||
},
|
||||
"DEVICECODE": {
|
||||
"TITLE": "Device Code",
|
||||
"DESCRIPTION": "Autorisieren Sie das Gerät auf einem Computer oder Smartphone."
|
||||
},
|
||||
"CUSTOM": {
|
||||
"TITLE": "Custom",
|
||||
"DESCRIPTION": "Deine Konfiguration entspricht keiner anderen Option."
|
||||
|
@ -1962,7 +1962,8 @@
|
||||
"GRANT": {
|
||||
"0": "Authorization Code",
|
||||
"1": "Implicit",
|
||||
"2": "Refresh Token"
|
||||
"2": "Refresh Token",
|
||||
"3": "Device Code"
|
||||
},
|
||||
"AUTHMETHOD": {
|
||||
"0": "Basic",
|
||||
@ -2053,6 +2054,10 @@
|
||||
"TITLE": "Implicit",
|
||||
"DESCRIPTION": "Get the tokens directly from the authorization endpoint"
|
||||
},
|
||||
"DEVICECODE": {
|
||||
"TITLE": "Device Code",
|
||||
"DESCRIPTION": "Authorize the device on a computer or smartphone."
|
||||
},
|
||||
"CUSTOM": {
|
||||
"TITLE": "Custom",
|
||||
"DESCRIPTION": "Your setting doesn't correspond to any other option."
|
||||
|
@ -1962,7 +1962,8 @@
|
||||
"GRANT": {
|
||||
"0": "Código de autorización",
|
||||
"1": "Implícito",
|
||||
"2": "Token de refresco"
|
||||
"2": "Token de refresco",
|
||||
"3": "Device Code"
|
||||
},
|
||||
"AUTHMETHOD": {
|
||||
"0": "Básico",
|
||||
@ -2053,6 +2054,10 @@
|
||||
"TITLE": "Implícita",
|
||||
"DESCRIPTION": "Obtén los tokens directamente del endpoint de autorización"
|
||||
},
|
||||
"DEVICECODE": {
|
||||
"TITLE": "Device Code",
|
||||
"DESCRIPTION": "Autorizar el dispositivo en una computadora o teléfono."
|
||||
},
|
||||
"CUSTOM": {
|
||||
"TITLE": "Personalizada",
|
||||
"DESCRIPTION": "Tu configuración no se corresponde con alguna de las otras opciones."
|
||||
|
@ -1966,7 +1966,8 @@
|
||||
"GRANT": {
|
||||
"0": "Code d'autorisation",
|
||||
"1": "Implicite",
|
||||
"2": "Rafraîchir le jeton"
|
||||
"2": "Rafraîchir le jeton",
|
||||
"3": "Device Code"
|
||||
},
|
||||
"AUTHMETHOD": {
|
||||
"0": "Basic",
|
||||
@ -2045,6 +2046,10 @@
|
||||
"TITLE": "Implicite",
|
||||
"DESCRIPTION": "Obtenir les jetons directement à partir du point final d'autorisation"
|
||||
},
|
||||
"DEVICECODE": {
|
||||
"TITLE": "Device Code",
|
||||
"DESCRIPTION": "Autoriser l'appareil sur un ordinateur ou un smartphone."
|
||||
},
|
||||
"CUSTOM": {
|
||||
"TITLE": "Personnalisé",
|
||||
"DESCRIPTION": "Votre paramètre ne correspond à aucune autre option."
|
||||
|
@ -1967,7 +1967,8 @@
|
||||
"GRANT": {
|
||||
"0": "Authorization Code",
|
||||
"1": "Implicit",
|
||||
"2": "Refresh Token"
|
||||
"2": "Refresh Token",
|
||||
"3": "Device Code"
|
||||
},
|
||||
"AUTHMETHOD": {
|
||||
"0": "Basic",
|
||||
@ -2058,6 +2059,10 @@
|
||||
"TITLE": "Implicit",
|
||||
"DESCRIPTION": "Ottenere i token direttamente dall'endpoint di autorizzazione"
|
||||
},
|
||||
"DEVICECODE": {
|
||||
"TITLE": "Device Code",
|
||||
"DESCRIPTION": "Autorizza il dispositivo su un computer o uno smartphone."
|
||||
},
|
||||
"CUSTOM": {
|
||||
"TITLE": "Custom",
|
||||
"DESCRIPTION": "La tua impostazione non corrisponde a nessun'altra opzione."
|
||||
|
@ -1957,7 +1957,8 @@
|
||||
"GRANT": {
|
||||
"0": "Authorization Code",
|
||||
"1": "Implicit",
|
||||
"2": "Refresh Token"
|
||||
"2": "Refresh Token",
|
||||
"3": "Device Code"
|
||||
},
|
||||
"AUTHMETHOD": {
|
||||
"0": "Basic",
|
||||
@ -2048,6 +2049,10 @@
|
||||
"TITLE": "Implicit",
|
||||
"DESCRIPTION": "認証エンドポイントから直接トークンを取得します。"
|
||||
},
|
||||
"DEVICECODE": {
|
||||
"TITLE": "Device Code",
|
||||
"DESCRIPTION": "コンピューターまたはスマートフォンでデバイスを認証します。"
|
||||
},
|
||||
"CUSTOM": {
|
||||
"TITLE": "Custom",
|
||||
"DESCRIPTION": "設定は他のオプションに対応していません。"
|
||||
|
@ -1966,7 +1966,8 @@
|
||||
"GRANT": {
|
||||
"0": "Kod autoryzacyjny",
|
||||
"1": "Implicite",
|
||||
"2": "Token odświeżający"
|
||||
"2": "Token odświeżający",
|
||||
"3": "Device Code"
|
||||
},
|
||||
"AUTHMETHOD": {
|
||||
"0": "Podstawowy",
|
||||
@ -2057,6 +2058,10 @@
|
||||
"TITLE": "Implicit",
|
||||
"DESCRIPTION": "Pobierz tokeny bezpośrednio z punktu autoryzacyjnego"
|
||||
},
|
||||
"DEVICECODE": {
|
||||
"TITLE": "Device Code",
|
||||
"DESCRIPTION": "Autoryzuj urządzenie na komputerze lub smartfonie."
|
||||
},
|
||||
"CUSTOM": {
|
||||
"TITLE": "Niestandardowy",
|
||||
"DESCRIPTION": "Twoje ustawienie nie odpowiada żadnej innej opcji."
|
||||
|
@ -1965,7 +1965,8 @@
|
||||
"GRANT": {
|
||||
"0": "Authorization Code",
|
||||
"1": "Implicit",
|
||||
"2": "Refresh Token"
|
||||
"2": "Refresh Token",
|
||||
"3": "Device Code"
|
||||
},
|
||||
"AUTHMETHOD": {
|
||||
"0": "Basic",
|
||||
@ -2044,6 +2045,10 @@
|
||||
"TITLE": "Implicit",
|
||||
"DESCRIPTION": "直接从授权端点获取令牌"
|
||||
},
|
||||
"DEVICECODE": {
|
||||
"TITLE": "Device Code",
|
||||
"DESCRIPTION": "在计算机或智能手机上授权设备。"
|
||||
},
|
||||
"CUSTOM": {
|
||||
"TITLE": "Custom",
|
||||
"DESCRIPTION": "您的设置与任何其他选项都不对应。"
|
||||
|
12
docs/.gitignore
vendored
12
docs/.gitignore
vendored
@ -9,6 +9,18 @@
|
||||
.cache-loader
|
||||
.artifacts
|
||||
|
||||
# Generated by docusaurus-plugin-openapi-docs
|
||||
docs/apis/auth
|
||||
docs/apis/mgmt
|
||||
docs/apis/admin
|
||||
docs/apis/system
|
||||
docs/apis/user_service
|
||||
docs/apis/session_service
|
||||
docs/apis/system
|
||||
docs/apis/user_service
|
||||
docs/apis/session_service
|
||||
docs/apis/settings_service
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
|
@ -1,27 +0,0 @@
|
||||
---
|
||||
title: Overview
|
||||
---
|
||||
|
||||
import {ListElement, ListWrapper, ICONTYPE} from '../../src/components/list';
|
||||
import Column from '../../src/components/column';
|
||||
|
||||
This section contains important agreements, policies and appendices relevant for users of our websites and services.
|
||||
|
||||
All documents will be provided in English language.
|
||||
<Column>
|
||||
<ListWrapper title="Main documents">
|
||||
<ListElement link="/docs/legal/terms-of-service" type={ICONTYPE.POLICY} title="Terms of Service" description="" />
|
||||
<ListElement link="/docs/legal/data-processing-agreement" type={ICONTYPE.POLICY} title="Data Processing Agreement" description="" />
|
||||
<ListElement link="/docs/legal/privacy-policy" type={ICONTYPE.POLICY} title="Privacy Policy" description="" />
|
||||
</ListWrapper>
|
||||
<ListWrapper title="Service">
|
||||
<ListElement link="/docs/legal/service-level-description" type={ICONTYPE.SERVICE} title="Service Level" description="Service levels offered by ZITADEL Cloud" />
|
||||
<ListElement link="/docs/legal/cloud-service-description" type={ICONTYPE.SERVICE} title="Cloud Service" description="Service description and data location" />
|
||||
<ListElement link="/docs/legal/terms-of-service-dedicated" type={ICONTYPE.SERVICE} title="Dedicated Instances" description="Terms and Conditions of dedicated Instances" />
|
||||
</ListWrapper>
|
||||
<ListWrapper title="Annexes">
|
||||
<ListElement link="/docs/legal/support-services" type={ICONTYPE.POLICY} title="Support Services" description="Support services offered by ZITADEL and CAOS Ltd." />
|
||||
<ListElement link="/docs/legal/acceptable-use-policy" type={ICONTYPE.POLICY} title="Acceptable Use Policy" description="Obligations while using ZITADEL Services" />
|
||||
<ListElement link="/docs/legal/rate-limit-policy" type={ICONTYPE.POLICY} title="Rate Limit Policy" description="How ZITADEL will use rate limiting" />
|
||||
</ListWrapper>
|
||||
</Column>
|
@ -1,39 +0,0 @@
|
||||
---
|
||||
title: Dedicated Instance Terms
|
||||
custom_edit_url: null
|
||||
---
|
||||
## General
|
||||
|
||||
Last revised: June 3, 2022
|
||||
|
||||
### Background
|
||||
|
||||
Within the scope of the Framework Agreement, the Customer may choose to purchase a subscription that requires a dedicated instance of ZITADEL. These additional terms for dedicated instance ("**Dedicated Instance Terms**") apply in addition to the Framework Agreement.
|
||||
|
||||
### Service
|
||||
|
||||
CAOS operates and manages a **Dedicated Instance** of ZITADEL in a private infrastructure environment dedicated for the Customer and provides support services for the Customer according the Purchase Order, these terms, agreed [**Service Level Description**](service-level-description), and [**Support Service Descriptions**](support-services).
|
||||
|
||||
Each Dedicated Instance consists, except agreed otherwise in writing, of a multi-zonal high-availability configuration that guarantees loads up to the specified [rate limits](rate-limit-policy#what-rate-limits-do-apply).
|
||||
|
||||
### Operations
|
||||
|
||||
CAOS will install and manage the Dedicated Instance on infracstructure provided by preferred cloud providers. Costs for infrastructure or cloud providers are not included in the Subscription, if not agreed otherwise in writing.
|
||||
|
||||
You may choose to provide the required infrastructure yourself. You must comply with the requirements and prerequisites outlined in the purchase order.
|
||||
|
||||
You may not modify, maintain or attempt to modify the Dedicated Instance, except with prior instructions by CAOS.
|
||||
|
||||
CAOS will use the same backup strategy as for ZITADEL Cloud (public cloud) services, except otherwise agreed between you and CAOS in writing.
|
||||
|
||||
### Maintenance and Updates
|
||||
|
||||
We will access, modify, and maintain the Dedicated Instance at times solely determined by CAOS (**"Regular Maintenance"**).
|
||||
|
||||
Under certain subscription plans, the Customer may agree a custom frequency and times for changes and updates. CAOS will coordinate the cadence and the changes with the Customer. To guarantee the quality of service, maintenance will occur on regular basis, typically monthly or sooner for security or performance related patches (**"Emergency Maintenance"**), but no longer than on quarterly basis.
|
||||
|
||||
If you fail to permit CAOS to conduct Regular Maintenance for 3 consecutive months or Emergency Maintenance within 5 days of notification, then CAOS will raise this issue with the Customer via Escalation Process. In case the issue is not resolved 5 days after such an escalation, CAOS may terminate the subscription with 30 days prior written notice to Customer. CAOS is not obligated to provide the service according to the terms and SLA, nor is CAOS liable to any security breach or damages after failure to permit Regular Maintenance for 3 consecutive months, or Emergency Maintenance for 5 days after notification.
|
||||
|
||||
### Incidents
|
||||
|
||||
Incidents are handled as documented in the [**Support Service Descriptions**](support-services). If the Customer choose in Purchase Order to provide the required infrastructure, then any incidents related to the infrastructure of the Dedicated Instance have to be resolved through the Customer directly.
|
@ -25,10 +25,6 @@ The following policies complement the TOS. When accepting the TOS, you accept th
|
||||
* [**Acceptable Use Policy**](acceptable-use-policy) - What we understand as acceptable and fair use of our Services
|
||||
* [**Rate Limit Policy**](rate-limit-policy) - How we avoid overloads of our services
|
||||
|
||||
This Agreement is extended with additional terms, in case your Subscription requires a Dedicated Instance. When you enter the Agreement with us, you accept these additional agreements.
|
||||
|
||||
* [**Dedicated Instance Terms**](terms-of-service-dedicated) - How we provide our services for a dedicated instance
|
||||
|
||||
### Alterations
|
||||
|
||||
Any provisions which deviate from these TOS must be agreed in writing between the Customer and us. Such agreements shall take precedence over the TOS outlined in this document.
|
||||
@ -195,7 +191,7 @@ Should any provision of these TOS be or become invalid, this shall not affect th
|
||||
|
||||
These TOS shall enter into force as of 15.07.2022.
|
||||
|
||||
Last revised: June 14, 2022
|
||||
Last revised: May 12, 2023
|
||||
|
||||
### Amendments
|
||||
|
||||
|
@ -4,7 +4,7 @@ services:
|
||||
traefik:
|
||||
networks:
|
||||
- 'zitadel'
|
||||
image: "traefik:v2.7"
|
||||
image: "traefik:v2.10.1"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
|
@ -1,3 +1,8 @@
|
||||
log:
|
||||
level: DEBUG
|
||||
|
||||
accessLog: {}
|
||||
|
||||
entrypoints:
|
||||
web:
|
||||
address: ":80"
|
||||
|
@ -23,3 +23,8 @@ Database:
|
||||
RootCert: "/crdb-certs/ca.crt"
|
||||
Cert: "/crdb-certs/client.root.crt"
|
||||
Key: "/crdb-certs/client.root.key"
|
||||
|
||||
LogStore:
|
||||
Access:
|
||||
Stdout:
|
||||
Enabled: true
|
||||
|
@ -70,3 +70,8 @@ This is the IAM admin users login according to your configuration in the [exampl
|
||||
- **password**: *RootPassword1!*
|
||||
|
||||
Read more about [the login process](/guides/integrate/login-users).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
You can connect to cockroach like this: `docker exec -it loadbalancing-example-my-cockroach-db-1 cockroach sql --host my-cockroach-db --certs-dir /cockroach/certs/`
|
||||
For example, to show all login names: `docker exec -it loadbalancing-example-my-cockroach-db-1 cockroach sql --database zitadel --host my-cockroach-db --certs-dir /cockroach/certs/ --execute "select * from projections.login_names2"`
|
||||
|
@ -83,7 +83,7 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
type: "doc",
|
||||
docId: "legal/introduction",
|
||||
docId: "legal",
|
||||
label: "Legal",
|
||||
position: "right",
|
||||
},
|
||||
@ -273,6 +273,13 @@ module.exports = {
|
||||
sidebarOptions: {
|
||||
groupPathsBy: "tag",
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
specPath: ".artifacts/openapi/zitadel/settings/v2alpha/settings_service.swagger.json",
|
||||
outputDir: "docs/apis/settings_service",
|
||||
sidebarOptions: {
|
||||
groupPathsBy: "tag",
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -15,7 +15,7 @@ http {
|
||||
}
|
||||
|
||||
location /docs {
|
||||
root /usr/share/nginx/html;
|
||||
alias /usr/share/nginx/html;
|
||||
index /docs/index.html;
|
||||
try_files $uri $uri/ /docs/index.html?q=$query_string;
|
||||
}
|
||||
|
@ -426,6 +426,20 @@ module.exports = {
|
||||
},
|
||||
items: require("./docs/apis/session_service/sidebar.js"),
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Settings Lifecycle (Alpha)",
|
||||
link: {
|
||||
type: "generated-index",
|
||||
title: "Settings Service API (Alpha)",
|
||||
slug: "/apis/settings_service",
|
||||
description:
|
||||
"This API is intended to manage settings in a ZITADEL instance.\n"+
|
||||
"\n"+
|
||||
"This project is in alpha state. It can AND will continue breaking until the services provide the same functionality as the current login.",
|
||||
},
|
||||
items: require("./docs/apis/settings_service/sidebar.js"),
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Assets",
|
||||
@ -526,7 +540,18 @@ module.exports = {
|
||||
support: [
|
||||
],
|
||||
legal: [
|
||||
"legal/introduction",
|
||||
{
|
||||
type: "category",
|
||||
label: "Legal Agreements",
|
||||
collapsed: false,
|
||||
link: {
|
||||
type: "generated-index",
|
||||
title: "Legal Agreements",
|
||||
slug: "legal",
|
||||
description:
|
||||
"This section contains important agreements, policies and appendices relevant for users of our websites and services. All documents will be provided in English language.",
|
||||
},
|
||||
items: [
|
||||
"legal/terms-of-service",
|
||||
"legal/data-processing-agreement",
|
||||
{
|
||||
@ -541,11 +566,10 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Additional terms",
|
||||
label: "Support Program",
|
||||
collapsed: true,
|
||||
items: [
|
||||
"legal/terms-support-service",
|
||||
"legal/terms-of-service-dedicated",
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -559,5 +583,7 @@ module.exports = {
|
||||
"legal/vulnerability-disclosure-policy",
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -2,7 +2,8 @@ import { Apps, ensureProjectExists, ensureProjectResourceDoesntExist } from '../
|
||||
import { Context } from 'support/commands';
|
||||
|
||||
const testProjectName = 'e2eprojectapplication';
|
||||
const testAppName = 'e2eappundertest';
|
||||
const testPKCEAppName = 'e2eapppkcetest';
|
||||
const testDEVICECODEAppName = 'e2eappdevicecodetest';
|
||||
|
||||
describe('applications', () => {
|
||||
beforeEach(() => {
|
||||
@ -17,15 +18,15 @@ describe('applications', () => {
|
||||
beforeEach(`ensure it doesn't exist already`, () => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
cy.get<string>('@projectId').then((projectId) => {
|
||||
ensureProjectResourceDoesntExist(ctx.api, projectId, Apps, testAppName);
|
||||
ensureProjectResourceDoesntExist(ctx.api, projectId, Apps, testPKCEAppName);
|
||||
cy.visit(`/projects/${projectId}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('add app', () => {
|
||||
it('add web pkce app', () => {
|
||||
cy.get('[data-e2e="app-card-add"]').should('be.visible').click();
|
||||
cy.get('[formcontrolname="name"]').focus().type(testAppName);
|
||||
cy.get('[formcontrolname="name"]').focus().type(testPKCEAppName);
|
||||
cy.get('[for="WEB"]').click();
|
||||
cy.get('[data-e2e="continue-button-nameandtype"]').click();
|
||||
cy.get('[for="PKCE"]').should('be.visible').click();
|
||||
@ -43,6 +44,33 @@ describe('applications', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('add native device code app', () => {
|
||||
beforeEach(`ensure it doesn't exist already`, () => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
cy.get<string>('@projectId').then((projectId) => {
|
||||
ensureProjectResourceDoesntExist(ctx.api, projectId, Apps, testDEVICECODEAppName);
|
||||
cy.visit(`/projects/${projectId}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('add device code app', () => {
|
||||
cy.get('[data-e2e="app-card-add"]').should('be.visible').click();
|
||||
cy.get('[formcontrolname="name"]').focus().type(testDEVICECODEAppName);
|
||||
cy.get('[for="N"]').click();
|
||||
cy.get('[data-e2e="continue-button-nameandtype"]').click();
|
||||
cy.get('[for="DEVICECODE"]').should('be.visible').click();
|
||||
cy.get('[data-e2e="continue-button-authmethod"]').click();
|
||||
cy.get('[data-e2e="create-button"]').click();
|
||||
cy.get('[id*=overlay]').should('exist');
|
||||
cy.shouldConfirmSuccess();
|
||||
const expectClientId = new RegExp(`^.*[0-9]+\\@${testProjectName}.*$`);
|
||||
cy.get('[data-e2e="client-id-copy"]').click();
|
||||
cy.contains('[data-e2e="client-id"]', expectClientId);
|
||||
cy.clipboardMatches(expectClientId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edit app', () => {
|
||||
it('should configure an application to enable dev mode');
|
||||
it('should configure an application to put user roles and info inside id token');
|
||||
|
@ -107,14 +107,9 @@ describe('quotas', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
expectCookieDoesntExist();
|
||||
const expiresMax = new Date();
|
||||
expiresMax.setMinutes(expiresMax.getMinutes() + 2);
|
||||
cy.getCookie('zitadel.quota.limiting').then((cookie) => {
|
||||
expect(cookie.value).to.equal('false');
|
||||
const cookieExpiry = new Date();
|
||||
cookieExpiry.setTime(cookie.expiry * 1000);
|
||||
expect(cookieExpiry).to.be.within(start, expiresMax);
|
||||
});
|
||||
cy.request({
|
||||
url: urls[0],
|
||||
method: 'GET',
|
||||
@ -127,12 +122,16 @@ describe('quotas', () => {
|
||||
});
|
||||
cy.getCookie('zitadel.quota.limiting').then((cookie) => {
|
||||
expect(cookie.value).to.equal('true');
|
||||
const cookieExpiry = new Date();
|
||||
cookieExpiry.setTime(cookie.expiry * 1000);
|
||||
expect(cookieExpiry).to.be.within(start, expiresMax);
|
||||
});
|
||||
createHumanUser(ctx.api, testUserName, false).then((res) => {
|
||||
expect(res.status).to.equal(429);
|
||||
});
|
||||
ensureQuotaIsRemoved(ctx, Unit.AuthenticatedRequests);
|
||||
createHumanUser(ctx.api, testUserName);
|
||||
expectCookieDoesntExist();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -301,3 +300,9 @@ describe('quotas', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function expectCookieDoesntExist() {
|
||||
cy.getCookie('zitadel.quota.limiting').then((cookie) => {
|
||||
expect(cookie).to.be.null;
|
||||
});
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import (
|
||||
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||
"github.com/zitadel/zitadel/internal/api/ui/login"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/logstore"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/metrics"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
@ -35,6 +34,8 @@ type API struct {
|
||||
http1HostName string
|
||||
grpcGateway *server.Gateway
|
||||
healthServer *health.Server
|
||||
accessInterceptor *http_mw.AccessInterceptor
|
||||
queries *query.Queries
|
||||
}
|
||||
|
||||
type healthCheck interface {
|
||||
@ -49,7 +50,7 @@ func New(
|
||||
verifier *internal_authz.TokenVerifier,
|
||||
authZ internal_authz.Config,
|
||||
tlsConfig *tls.Config, http2HostName, http1HostName string,
|
||||
accessSvc *logstore.Service,
|
||||
accessInterceptor *http_mw.AccessInterceptor,
|
||||
) (_ *API, err error) {
|
||||
api := &API{
|
||||
port: port,
|
||||
@ -57,10 +58,12 @@ func New(
|
||||
health: queries,
|
||||
router: router,
|
||||
http1HostName: http1HostName,
|
||||
queries: queries,
|
||||
accessInterceptor: accessInterceptor,
|
||||
}
|
||||
|
||||
api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName, tlsConfig, accessSvc)
|
||||
api.grpcGateway, err = server.CreateGateway(ctx, port, http1HostName)
|
||||
api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName, tlsConfig, accessInterceptor.AccessService())
|
||||
api.grpcGateway, err = server.CreateGateway(ctx, port, http1HostName, accessInterceptor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -79,7 +82,14 @@ func New(
|
||||
// used for v1 api (system, admin, mgmt, auth)
|
||||
func (a *API) RegisterServer(ctx context.Context, grpcServer server.WithGatewayPrefix) error {
|
||||
grpcServer.RegisterServer(a.grpcServer)
|
||||
handler, prefix, err := server.CreateGatewayWithPrefix(ctx, grpcServer, a.port, a.http1HostName)
|
||||
handler, prefix, err := server.CreateGatewayWithPrefix(
|
||||
ctx,
|
||||
grpcServer,
|
||||
a.port,
|
||||
a.http1HostName,
|
||||
a.accessInterceptor,
|
||||
a.queries,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v2alpha"
|
||||
@ -36,3 +39,13 @@ func ListQueryToQuery(query *object.ListQuery) (offset, limit uint64, asc bool)
|
||||
}
|
||||
return query.Offset, uint64(query.Limit), query.Asc
|
||||
}
|
||||
|
||||
func ResourceOwnerFromReq(ctx context.Context, req *object.RequestContext) string {
|
||||
if req.GetInstance() {
|
||||
return authz.GetInstance(ctx).InstanceID()
|
||||
}
|
||||
if req.GetOrgId() != "" {
|
||||
return req.GetOrgId()
|
||||
}
|
||||
return authz.GetCtxData(ctx).OrgID
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
client_middleware "github.com/zitadel/zitadel/internal/api/grpc/client/middleware"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server/middleware"
|
||||
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -67,10 +68,12 @@ type Gateway struct {
|
||||
mux *runtime.ServeMux
|
||||
http1HostName string
|
||||
connection *grpc.ClientConn
|
||||
accessInterceptor *http_mw.AccessInterceptor
|
||||
queries *query.Queries
|
||||
}
|
||||
|
||||
func (g *Gateway) Handler() http.Handler {
|
||||
return addInterceptors(g.mux, g.http1HostName)
|
||||
return addInterceptors(g.mux, g.http1HostName, g.accessInterceptor, g.queries)
|
||||
}
|
||||
|
||||
type CustomHTTPResponse interface {
|
||||
@ -79,7 +82,14 @@ type CustomHTTPResponse interface {
|
||||
|
||||
type RegisterGatewayFunc func(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error
|
||||
|
||||
func CreateGatewayWithPrefix(ctx context.Context, g WithGatewayPrefix, port uint16, http1HostName string) (http.Handler, string, error) {
|
||||
func CreateGatewayWithPrefix(
|
||||
ctx context.Context,
|
||||
g WithGatewayPrefix,
|
||||
port uint16,
|
||||
http1HostName string,
|
||||
accessInterceptor *http_mw.AccessInterceptor,
|
||||
queries *query.Queries,
|
||||
) (http.Handler, string, error) {
|
||||
runtimeMux := runtime.NewServeMux(serveMuxOptions...)
|
||||
opts := []grpc.DialOption{
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
@ -93,10 +103,10 @@ func CreateGatewayWithPrefix(ctx context.Context, g WithGatewayPrefix, port uint
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to register grpc gateway: %w", err)
|
||||
}
|
||||
return addInterceptors(runtimeMux, http1HostName), g.GatewayPathPrefix(), nil
|
||||
return addInterceptors(runtimeMux, http1HostName, accessInterceptor, queries), g.GatewayPathPrefix(), nil
|
||||
}
|
||||
|
||||
func CreateGateway(ctx context.Context, port uint16, http1HostName string) (*Gateway, error) {
|
||||
func CreateGateway(ctx context.Context, port uint16, http1HostName string, accessInterceptor *http_mw.AccessInterceptor) (*Gateway, error) {
|
||||
connection, err := dial(ctx,
|
||||
port,
|
||||
[]grpc.DialOption{
|
||||
@ -111,6 +121,7 @@ func CreateGateway(ctx context.Context, port uint16, http1HostName string) (*Gat
|
||||
mux: runtimeMux,
|
||||
http1HostName: http1HostName,
|
||||
connection: connection,
|
||||
accessInterceptor: accessInterceptor,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -145,13 +156,22 @@ func dial(ctx context.Context, port uint16, opts []grpc.DialOption) (*grpc.Clien
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func addInterceptors(handler http.Handler, http1HostName string) http.Handler {
|
||||
func addInterceptors(
|
||||
handler http.Handler,
|
||||
http1HostName string,
|
||||
accessInterceptor *http_mw.AccessInterceptor,
|
||||
queries *query.Queries,
|
||||
) http.Handler {
|
||||
handler = http_mw.CallDurationHandler(handler)
|
||||
handler = http1Host(handler, http1HostName)
|
||||
handler = http_mw.CORSInterceptor(handler)
|
||||
handler = http_mw.RobotsTagHandler(handler)
|
||||
handler = http_mw.DefaultTelemetryHandler(handler)
|
||||
return http_mw.DefaultMetricsHandler(handler)
|
||||
// For some non-obvious reason, the exhaustedCookieInterceptor sends the SetCookie header
|
||||
// only if it follows the http_mw.DefaultTelemetryHandler
|
||||
handler = exhaustedCookieInterceptor(handler, accessInterceptor, queries)
|
||||
handler = http_mw.DefaultMetricsHandler(handler)
|
||||
return handler
|
||||
}
|
||||
|
||||
func http1Host(next http.Handler, http1HostName string) http.Handler {
|
||||
@ -165,3 +185,35 @@ func http1Host(next http.Handler, http1HostName string) http.Handler {
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func exhaustedCookieInterceptor(
|
||||
next http.Handler,
|
||||
accessInterceptor *http_mw.AccessInterceptor,
|
||||
queries *query.Queries,
|
||||
) http.Handler {
|
||||
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
next.ServeHTTP(&cookieResponseWriter{
|
||||
ResponseWriter: writer,
|
||||
accessInterceptor: accessInterceptor,
|
||||
request: request,
|
||||
queries: queries,
|
||||
}, request)
|
||||
})
|
||||
}
|
||||
|
||||
type cookieResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
accessInterceptor *http_mw.AccessInterceptor
|
||||
request *http.Request
|
||||
queries *query.Queries
|
||||
}
|
||||
|
||||
func (r *cookieResponseWriter) WriteHeader(status int) {
|
||||
if status >= 200 && status < 300 {
|
||||
r.accessInterceptor.DeleteExhaustedCookie(r.ResponseWriter, r.request)
|
||||
}
|
||||
if status == http.StatusTooManyRequests {
|
||||
r.accessInterceptor.SetExhaustedCookie(r.ResponseWriter, r.request)
|
||||
}
|
||||
r.ResponseWriter.WriteHeader(status)
|
||||
}
|
||||
|
57
internal/api/grpc/settings/v2/server.go
Normal file
57
internal/api/grpc/settings/v2/server.go
Normal file
@ -0,0 +1,57 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/assets"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2alpha"
|
||||
)
|
||||
|
||||
var _ settings.SettingsServiceServer = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
settings.UnimplementedSettingsServiceServer
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
assetsAPIDomain func(context.Context) string
|
||||
}
|
||||
|
||||
type Config struct{}
|
||||
|
||||
func CreateServer(
|
||||
command *command.Commands,
|
||||
query *query.Queries,
|
||||
externalSecure bool,
|
||||
) *Server {
|
||||
return &Server{
|
||||
command: command,
|
||||
query: query,
|
||||
assetsAPIDomain: assets.AssetAPI(externalSecure),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
|
||||
settings.RegisterSettingsServiceServer(grpcServer, s)
|
||||
}
|
||||
|
||||
func (s *Server) AppName() string {
|
||||
return settings.SettingsService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) MethodPrefix() string {
|
||||
return settings.SettingsService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) AuthMethods() authz.MethodMapping {
|
||||
return settings.SettingsService_AuthMethods
|
||||
}
|
||||
|
||||
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
|
||||
return settings.RegisterSettingsServiceHandler
|
||||
}
|
129
internal/api/grpc/settings/v2/settings.go
Normal file
129
internal/api/grpc/settings/v2/settings.go
Normal file
@ -0,0 +1,129 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/text"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
object_pb "github.com/zitadel/zitadel/pkg/grpc/object/v2alpha"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/settings/v2alpha"
|
||||
)
|
||||
|
||||
func (s *Server) GetLoginSettings(ctx context.Context, req *settings.GetLoginSettingsRequest) (*settings.GetLoginSettingsResponse, error) {
|
||||
current, err := s.query.LoginPolicyByID(ctx, true, object.ResourceOwnerFromReq(ctx, req.GetCtx()), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &settings.GetLoginSettingsResponse{
|
||||
Settings: loginSettingsToPb(current),
|
||||
Details: &object_pb.Details{
|
||||
Sequence: current.Sequence,
|
||||
ChangeDate: timestamppb.New(current.ChangeDate),
|
||||
ResourceOwner: current.OrgID,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetPasswordComplexitySettings(ctx context.Context, req *settings.GetPasswordComplexitySettingsRequest) (*settings.GetPasswordComplexitySettingsResponse, error) {
|
||||
current, err := s.query.PasswordComplexityPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.GetCtx()), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &settings.GetPasswordComplexitySettingsResponse{
|
||||
Settings: passwordSettingsToPb(current),
|
||||
Details: &object_pb.Details{
|
||||
Sequence: current.Sequence,
|
||||
ChangeDate: timestamppb.New(current.ChangeDate),
|
||||
ResourceOwner: current.ResourceOwner,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetBrandingSettings(ctx context.Context, req *settings.GetBrandingSettingsRequest) (*settings.GetBrandingSettingsResponse, error) {
|
||||
current, err := s.query.ActiveLabelPolicyByOrg(ctx, object.ResourceOwnerFromReq(ctx, req.GetCtx()), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &settings.GetBrandingSettingsResponse{
|
||||
Settings: brandingSettingsToPb(current, s.assetsAPIDomain(ctx)),
|
||||
Details: &object_pb.Details{
|
||||
Sequence: current.Sequence,
|
||||
ChangeDate: timestamppb.New(current.ChangeDate),
|
||||
ResourceOwner: current.ResourceOwner,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetDomainSettings(ctx context.Context, req *settings.GetDomainSettingsRequest) (*settings.GetDomainSettingsResponse, error) {
|
||||
current, err := s.query.DomainPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.GetCtx()), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &settings.GetDomainSettingsResponse{
|
||||
Settings: domainSettingsToPb(current),
|
||||
Details: &object_pb.Details{
|
||||
Sequence: current.Sequence,
|
||||
ChangeDate: timestamppb.New(current.ChangeDate),
|
||||
ResourceOwner: current.ResourceOwner,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetLegalAndSupportSettings(ctx context.Context, req *settings.GetLegalAndSupportSettingsRequest) (*settings.GetLegalAndSupportSettingsResponse, error) {
|
||||
current, err := s.query.PrivacyPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.GetCtx()), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &settings.GetLegalAndSupportSettingsResponse{
|
||||
Settings: legalAndSupportSettingsToPb(current),
|
||||
Details: &object_pb.Details{
|
||||
Sequence: current.Sequence,
|
||||
ChangeDate: timestamppb.New(current.ChangeDate),
|
||||
ResourceOwner: current.ResourceOwner,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetLockoutSettings(ctx context.Context, req *settings.GetLockoutSettingsRequest) (*settings.GetLockoutSettingsResponse, error) {
|
||||
current, err := s.query.LockoutPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.GetCtx()), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &settings.GetLockoutSettingsResponse{
|
||||
Settings: lockoutSettingsToPb(current),
|
||||
Details: &object_pb.Details{
|
||||
Sequence: current.Sequence,
|
||||
ChangeDate: timestamppb.New(current.ChangeDate),
|
||||
ResourceOwner: current.ResourceOwner,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetActiveIdentityProviders(ctx context.Context, req *settings.GetActiveIdentityProvidersRequest) (*settings.GetActiveIdentityProvidersResponse, error) {
|
||||
links, err := s.query.IDPLoginPolicyLinks(ctx, object.ResourceOwnerFromReq(ctx, req.GetCtx()), &query.IDPLoginPolicyLinksSearchQuery{}, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &settings.GetActiveIdentityProvidersResponse{
|
||||
Details: object.ToListDetails(links.SearchResponse),
|
||||
IdentityProviders: identityProvidersToPb(links.Links),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetGeneralSettings(ctx context.Context, _ *settings.GetGeneralSettingsRequest) (*settings.GetGeneralSettingsResponse, error) {
|
||||
langs, err := s.query.Languages(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instance := authz.GetInstance(ctx)
|
||||
return &settings.GetGeneralSettingsResponse{
|
||||
SupportedLanguages: text.LanguageTagsToStrings(langs),
|
||||
DefaultOrgId: instance.DefaultOrganisationID(),
|
||||
DefaultLanguage: instance.DefaultLanguage().String(),
|
||||
}, nil
|
||||
}
|
189
internal/api/grpc/settings/v2/settings_converter.go
Normal file
189
internal/api/grpc/settings/v2/settings_converter.go
Normal file
@ -0,0 +1,189 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2alpha"
|
||||
)
|
||||
|
||||
// TODO: ?
|
||||
func loginSettingsToPb(current *query.LoginPolicy) *settings.LoginSettings {
|
||||
multi := make([]settings.MultiFactorType, len(current.MultiFactors))
|
||||
for i, typ := range current.MultiFactors {
|
||||
multi[i] = multiFactorTypeToPb(typ)
|
||||
}
|
||||
second := make([]settings.SecondFactorType, len(current.SecondFactors))
|
||||
for i, typ := range current.SecondFactors {
|
||||
second[i] = secondFactorTypeToPb(typ)
|
||||
}
|
||||
|
||||
return &settings.LoginSettings{
|
||||
AllowUsernamePassword: current.AllowUsernamePassword,
|
||||
AllowRegister: current.AllowRegister,
|
||||
AllowExternalIdp: current.AllowExternalIDPs,
|
||||
ForceMfa: current.ForceMFA,
|
||||
PasskeysType: passkeysTypeToPb(current.PasswordlessType),
|
||||
HidePasswordReset: current.HidePasswordReset,
|
||||
IgnoreUnknownUsernames: current.IgnoreUnknownUsernames,
|
||||
AllowDomainDiscovery: current.AllowDomainDiscovery,
|
||||
DisableLoginWithEmail: current.DisableLoginWithEmail,
|
||||
DisableLoginWithPhone: current.DisableLoginWithPhone,
|
||||
DefaultRedirectUri: current.DefaultRedirectURI,
|
||||
PasswordCheckLifetime: durationpb.New(current.PasswordCheckLifetime),
|
||||
ExternalLoginCheckLifetime: durationpb.New(current.ExternalLoginCheckLifetime),
|
||||
MfaInitSkipLifetime: durationpb.New(current.MFAInitSkipLifetime),
|
||||
SecondFactorCheckLifetime: durationpb.New(current.SecondFactorCheckLifetime),
|
||||
MultiFactorCheckLifetime: durationpb.New(current.MultiFactorCheckLifetime),
|
||||
SecondFactors: second,
|
||||
MultiFactors: multi,
|
||||
ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault),
|
||||
}
|
||||
}
|
||||
|
||||
func isDefaultToResourceOwnerTypePb(isDefault bool) settings.ResourceOwnerType {
|
||||
if isDefault {
|
||||
return settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE
|
||||
}
|
||||
return settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_ORG
|
||||
}
|
||||
|
||||
func passkeysTypeToPb(passwordlessType domain.PasswordlessType) settings.PasskeysType {
|
||||
switch passwordlessType {
|
||||
case domain.PasswordlessTypeAllowed:
|
||||
return settings.PasskeysType_PASSKEYS_TYPE_ALLOWED
|
||||
case domain.PasswordlessTypeNotAllowed:
|
||||
return settings.PasskeysType_PASSKEYS_TYPE_NOT_ALLOWED
|
||||
default:
|
||||
return settings.PasskeysType_PASSKEYS_TYPE_NOT_ALLOWED
|
||||
}
|
||||
}
|
||||
|
||||
func secondFactorTypeToPb(secondFactorType domain.SecondFactorType) settings.SecondFactorType {
|
||||
switch secondFactorType {
|
||||
case domain.SecondFactorTypeOTP:
|
||||
return settings.SecondFactorType_SECOND_FACTOR_TYPE_OTP
|
||||
case domain.SecondFactorTypeU2F:
|
||||
return settings.SecondFactorType_SECOND_FACTOR_TYPE_U2F
|
||||
case domain.SecondFactorTypeUnspecified:
|
||||
return settings.SecondFactorType_SECOND_FACTOR_TYPE_UNSPECIFIED
|
||||
default:
|
||||
return settings.SecondFactorType_SECOND_FACTOR_TYPE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func multiFactorTypeToPb(typ domain.MultiFactorType) settings.MultiFactorType {
|
||||
switch typ {
|
||||
case domain.MultiFactorTypeU2FWithPIN:
|
||||
return settings.MultiFactorType_MULTI_FACTOR_TYPE_U2F_WITH_VERIFICATION
|
||||
case domain.MultiFactorTypeUnspecified:
|
||||
return settings.MultiFactorType_MULTI_FACTOR_TYPE_UNSPECIFIED
|
||||
default:
|
||||
return settings.MultiFactorType_MULTI_FACTOR_TYPE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func passwordSettingsToPb(current *query.PasswordComplexityPolicy) *settings.PasswordComplexitySettings {
|
||||
return &settings.PasswordComplexitySettings{
|
||||
MinLength: current.MinLength,
|
||||
RequiresUppercase: current.HasUppercase,
|
||||
RequiresLowercase: current.HasLowercase,
|
||||
RequiresNumber: current.HasNumber,
|
||||
RequiresSymbol: current.HasSymbol,
|
||||
ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault),
|
||||
}
|
||||
}
|
||||
|
||||
func brandingSettingsToPb(current *query.LabelPolicy, assetPrefix string) *settings.BrandingSettings {
|
||||
return &settings.BrandingSettings{
|
||||
LightTheme: themeToPb(current.Light, assetPrefix, current.ResourceOwner),
|
||||
DarkTheme: themeToPb(current.Dark, assetPrefix, current.ResourceOwner),
|
||||
FontUrl: domain.AssetURL(assetPrefix, current.ResourceOwner, current.FontURL),
|
||||
DisableWatermark: current.WatermarkDisabled,
|
||||
HideLoginNameSuffix: current.HideLoginNameSuffix,
|
||||
ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault),
|
||||
}
|
||||
}
|
||||
|
||||
func themeToPb(theme query.Theme, assetPrefix, resourceOwner string) *settings.Theme {
|
||||
return &settings.Theme{
|
||||
PrimaryColor: theme.PrimaryColor,
|
||||
BackgroundColor: theme.BackgroundColor,
|
||||
FontColor: theme.FontColor,
|
||||
WarnColor: theme.WarnColor,
|
||||
LogoUrl: domain.AssetURL(assetPrefix, resourceOwner, theme.LogoURL),
|
||||
IconUrl: domain.AssetURL(assetPrefix, resourceOwner, theme.IconURL),
|
||||
}
|
||||
}
|
||||
|
||||
func domainSettingsToPb(current *query.DomainPolicy) *settings.DomainSettings {
|
||||
return &settings.DomainSettings{
|
||||
LoginNameIncludesDomain: current.UserLoginMustBeDomain,
|
||||
RequireOrgDomainVerification: current.ValidateOrgDomains,
|
||||
SmtpSenderAddressMatchesInstanceDomain: current.SMTPSenderAddressMatchesInstanceDomain,
|
||||
ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault),
|
||||
}
|
||||
}
|
||||
|
||||
func legalAndSupportSettingsToPb(current *query.PrivacyPolicy) *settings.LegalAndSupportSettings {
|
||||
return &settings.LegalAndSupportSettings{
|
||||
TosLink: current.TOSLink,
|
||||
PrivacyPolicyLink: current.PrivacyLink,
|
||||
HelpLink: current.HelpLink,
|
||||
SupportEmail: string(current.SupportEmail),
|
||||
ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault),
|
||||
}
|
||||
}
|
||||
|
||||
func lockoutSettingsToPb(current *query.LockoutPolicy) *settings.LockoutSettings {
|
||||
return &settings.LockoutSettings{
|
||||
MaxPasswordAttempts: current.MaxPasswordAttempts,
|
||||
ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault),
|
||||
}
|
||||
}
|
||||
|
||||
func identityProvidersToPb(idps []*query.IDPLoginPolicyLink) []*settings.IdentityProvider {
|
||||
providers := make([]*settings.IdentityProvider, len(idps))
|
||||
for i, idp := range idps {
|
||||
providers[i] = identityProviderToPb(idp)
|
||||
}
|
||||
return providers
|
||||
}
|
||||
|
||||
func identityProviderToPb(idp *query.IDPLoginPolicyLink) *settings.IdentityProvider {
|
||||
return &settings.IdentityProvider{
|
||||
Id: idp.IDPID,
|
||||
Name: domain.IDPName(idp.IDPName, idp.IDPType),
|
||||
Type: idpTypeToPb(idp.IDPType),
|
||||
}
|
||||
}
|
||||
|
||||
func idpTypeToPb(idpType domain.IDPType) settings.IdentityProviderType {
|
||||
switch idpType {
|
||||
case domain.IDPTypeUnspecified:
|
||||
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_UNSPECIFIED
|
||||
case domain.IDPTypeOIDC:
|
||||
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_OIDC
|
||||
case domain.IDPTypeJWT:
|
||||
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_JWT
|
||||
case domain.IDPTypeOAuth:
|
||||
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_OAUTH
|
||||
case domain.IDPTypeLDAP:
|
||||
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_LDAP
|
||||
case domain.IDPTypeAzureAD:
|
||||
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_AZURE_AD
|
||||
case domain.IDPTypeGitHub:
|
||||
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITHUB
|
||||
case domain.IDPTypeGitHubEnterprise:
|
||||
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITHUB_ES
|
||||
case domain.IDPTypeGitLab:
|
||||
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITLAB
|
||||
case domain.IDPTypeGitLabSelfHosted:
|
||||
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITLAB_SELF_HOSTED
|
||||
case domain.IDPTypeGoogle:
|
||||
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GOOGLE
|
||||
default:
|
||||
return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_UNSPECIFIED
|
||||
}
|
||||
}
|
461
internal/api/grpc/settings/v2/settings_converter_test.go
Normal file
461
internal/api/grpc/settings/v2/settings_converter_test.go
Normal file
@ -0,0 +1,461 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2alpha"
|
||||
)
|
||||
|
||||
var ignoreMessageTypes = map[protoreflect.FullName]bool{
|
||||
"google.protobuf.Duration": true,
|
||||
}
|
||||
|
||||
// allFieldsSet recusively checks if all values in a message
|
||||
// have a non-zero value.
|
||||
func allFieldsSet(t testing.TB, msg protoreflect.Message) {
|
||||
md := msg.Descriptor()
|
||||
name := md.FullName()
|
||||
if ignoreMessageTypes[name] {
|
||||
return
|
||||
}
|
||||
|
||||
fields := md.Fields()
|
||||
|
||||
for i := 0; i < fields.Len(); i++ {
|
||||
fd := fields.Get(i)
|
||||
if !msg.Has(fd) {
|
||||
t.Errorf("not all fields set in %q, missing %q", name, fd.Name())
|
||||
continue
|
||||
}
|
||||
|
||||
if fd.Kind() == protoreflect.MessageKind {
|
||||
allFieldsSet(t, msg.Get(fd).Message())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_loginSettingsToPb(t *testing.T) {
|
||||
arg := &query.LoginPolicy{
|
||||
AllowUsernamePassword: true,
|
||||
AllowRegister: true,
|
||||
AllowExternalIDPs: true,
|
||||
ForceMFA: true,
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
DisableLoginWithEmail: true,
|
||||
DisableLoginWithPhone: true,
|
||||
DefaultRedirectURI: "example.com",
|
||||
PasswordCheckLifetime: time.Hour,
|
||||
ExternalLoginCheckLifetime: time.Minute,
|
||||
MFAInitSkipLifetime: time.Millisecond,
|
||||
SecondFactorCheckLifetime: time.Microsecond,
|
||||
MultiFactorCheckLifetime: time.Nanosecond,
|
||||
SecondFactors: []domain.SecondFactorType{
|
||||
domain.SecondFactorTypeOTP,
|
||||
domain.SecondFactorTypeU2F,
|
||||
},
|
||||
MultiFactors: []domain.MultiFactorType{
|
||||
domain.MultiFactorTypeU2FWithPIN,
|
||||
},
|
||||
IsDefault: true,
|
||||
}
|
||||
|
||||
want := &settings.LoginSettings{
|
||||
AllowUsernamePassword: true,
|
||||
AllowRegister: true,
|
||||
AllowExternalIdp: true,
|
||||
ForceMfa: true,
|
||||
PasskeysType: settings.PasskeysType_PASSKEYS_TYPE_ALLOWED,
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
DisableLoginWithEmail: true,
|
||||
DisableLoginWithPhone: true,
|
||||
DefaultRedirectUri: "example.com",
|
||||
PasswordCheckLifetime: durationpb.New(time.Hour),
|
||||
ExternalLoginCheckLifetime: durationpb.New(time.Minute),
|
||||
MfaInitSkipLifetime: durationpb.New(time.Millisecond),
|
||||
SecondFactorCheckLifetime: durationpb.New(time.Microsecond),
|
||||
MultiFactorCheckLifetime: durationpb.New(time.Nanosecond),
|
||||
SecondFactors: []settings.SecondFactorType{
|
||||
settings.SecondFactorType_SECOND_FACTOR_TYPE_OTP,
|
||||
settings.SecondFactorType_SECOND_FACTOR_TYPE_U2F,
|
||||
},
|
||||
MultiFactors: []settings.MultiFactorType{
|
||||
settings.MultiFactorType_MULTI_FACTOR_TYPE_U2F_WITH_VERIFICATION,
|
||||
},
|
||||
ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE,
|
||||
}
|
||||
|
||||
got := loginSettingsToPb(arg)
|
||||
allFieldsSet(t, got.ProtoReflect())
|
||||
if !proto.Equal(got, want) {
|
||||
t.Errorf("loginSettingsToPb() =\n%v\nwant\n%v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_isDefaultToResourceOwnerTypePb(t *testing.T) {
|
||||
type args struct {
|
||||
isDefault bool
|
||||
}
|
||||
tests := []struct {
|
||||
args args
|
||||
want settings.ResourceOwnerType
|
||||
}{
|
||||
{
|
||||
args: args{false},
|
||||
want: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_ORG,
|
||||
},
|
||||
{
|
||||
args: args{true},
|
||||
want: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.want.String(), func(t *testing.T) {
|
||||
got := isDefaultToResourceOwnerTypePb(tt.args.isDefault)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_passkeysTypeToPb(t *testing.T) {
|
||||
type args struct {
|
||||
passwordlessType domain.PasswordlessType
|
||||
}
|
||||
tests := []struct {
|
||||
args args
|
||||
want settings.PasskeysType
|
||||
}{
|
||||
{
|
||||
args: args{domain.PasswordlessTypeNotAllowed},
|
||||
want: settings.PasskeysType_PASSKEYS_TYPE_NOT_ALLOWED,
|
||||
},
|
||||
{
|
||||
args: args{domain.PasswordlessTypeAllowed},
|
||||
want: settings.PasskeysType_PASSKEYS_TYPE_ALLOWED,
|
||||
},
|
||||
{
|
||||
args: args{99},
|
||||
want: settings.PasskeysType_PASSKEYS_TYPE_NOT_ALLOWED,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.want.String(), func(t *testing.T) {
|
||||
got := passkeysTypeToPb(tt.args.passwordlessType)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_secondFactorTypeToPb(t *testing.T) {
|
||||
type args struct {
|
||||
secondFactorType domain.SecondFactorType
|
||||
}
|
||||
tests := []struct {
|
||||
args args
|
||||
want settings.SecondFactorType
|
||||
}{
|
||||
{
|
||||
args: args{domain.SecondFactorTypeOTP},
|
||||
want: settings.SecondFactorType_SECOND_FACTOR_TYPE_OTP,
|
||||
},
|
||||
{
|
||||
args: args{domain.SecondFactorTypeU2F},
|
||||
want: settings.SecondFactorType_SECOND_FACTOR_TYPE_U2F,
|
||||
},
|
||||
{
|
||||
args: args{domain.SecondFactorTypeUnspecified},
|
||||
want: settings.SecondFactorType_SECOND_FACTOR_TYPE_UNSPECIFIED,
|
||||
},
|
||||
{
|
||||
args: args{99},
|
||||
want: settings.SecondFactorType_SECOND_FACTOR_TYPE_UNSPECIFIED,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.want.String(), func(t *testing.T) {
|
||||
got := secondFactorTypeToPb(tt.args.secondFactorType)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_multiFactorTypeToPb(t *testing.T) {
|
||||
type args struct {
|
||||
typ domain.MultiFactorType
|
||||
}
|
||||
tests := []struct {
|
||||
args args
|
||||
want settings.MultiFactorType
|
||||
}{
|
||||
{
|
||||
args: args{domain.MultiFactorTypeU2FWithPIN},
|
||||
want: settings.MultiFactorType_MULTI_FACTOR_TYPE_U2F_WITH_VERIFICATION,
|
||||
},
|
||||
{
|
||||
args: args{domain.MultiFactorTypeUnspecified},
|
||||
want: settings.MultiFactorType_MULTI_FACTOR_TYPE_UNSPECIFIED,
|
||||
},
|
||||
{
|
||||
args: args{99},
|
||||
want: settings.MultiFactorType_MULTI_FACTOR_TYPE_UNSPECIFIED,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.want.String(), func(t *testing.T) {
|
||||
got := multiFactorTypeToPb(tt.args.typ)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_passwordSettingsToPb(t *testing.T) {
|
||||
arg := &query.PasswordComplexityPolicy{
|
||||
MinLength: 12,
|
||||
HasUppercase: true,
|
||||
HasLowercase: true,
|
||||
HasNumber: true,
|
||||
HasSymbol: true,
|
||||
IsDefault: true,
|
||||
}
|
||||
want := &settings.PasswordComplexitySettings{
|
||||
MinLength: 12,
|
||||
RequiresUppercase: true,
|
||||
RequiresLowercase: true,
|
||||
RequiresNumber: true,
|
||||
RequiresSymbol: true,
|
||||
ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE,
|
||||
}
|
||||
|
||||
got := passwordSettingsToPb(arg)
|
||||
allFieldsSet(t, got.ProtoReflect())
|
||||
if !proto.Equal(got, want) {
|
||||
t.Errorf("passwordSettingsToPb() =\n%v\nwant\n%v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_brandingSettingsToPb(t *testing.T) {
|
||||
arg := &query.LabelPolicy{
|
||||
Light: query.Theme{
|
||||
PrimaryColor: "red",
|
||||
WarnColor: "white",
|
||||
BackgroundColor: "blue",
|
||||
FontColor: "orange",
|
||||
LogoURL: "light-logo",
|
||||
IconURL: "light-icon",
|
||||
},
|
||||
Dark: query.Theme{
|
||||
PrimaryColor: "magenta",
|
||||
WarnColor: "pink",
|
||||
BackgroundColor: "black",
|
||||
FontColor: "white",
|
||||
LogoURL: "dark-logo",
|
||||
IconURL: "dark-icon",
|
||||
},
|
||||
ResourceOwner: "me",
|
||||
FontURL: "fonts",
|
||||
WatermarkDisabled: true,
|
||||
HideLoginNameSuffix: true,
|
||||
IsDefault: true,
|
||||
}
|
||||
want := &settings.BrandingSettings{
|
||||
LightTheme: &settings.Theme{
|
||||
PrimaryColor: "red",
|
||||
WarnColor: "white",
|
||||
BackgroundColor: "blue",
|
||||
FontColor: "orange",
|
||||
LogoUrl: "http://example.com/me/light-logo",
|
||||
IconUrl: "http://example.com/me/light-icon",
|
||||
},
|
||||
DarkTheme: &settings.Theme{
|
||||
PrimaryColor: "magenta",
|
||||
WarnColor: "pink",
|
||||
BackgroundColor: "black",
|
||||
FontColor: "white",
|
||||
LogoUrl: "http://example.com/me/dark-logo",
|
||||
IconUrl: "http://example.com/me/dark-icon",
|
||||
},
|
||||
FontUrl: "http://example.com/me/fonts",
|
||||
DisableWatermark: true,
|
||||
HideLoginNameSuffix: true,
|
||||
ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE,
|
||||
}
|
||||
|
||||
got := brandingSettingsToPb(arg, "http://example.com")
|
||||
allFieldsSet(t, got.ProtoReflect())
|
||||
if !proto.Equal(got, want) {
|
||||
t.Errorf("brandingSettingsToPb() =\n%v\nwant\n%v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_domainSettingsToPb(t *testing.T) {
|
||||
arg := &query.DomainPolicy{
|
||||
UserLoginMustBeDomain: true,
|
||||
ValidateOrgDomains: true,
|
||||
SMTPSenderAddressMatchesInstanceDomain: true,
|
||||
IsDefault: true,
|
||||
}
|
||||
want := &settings.DomainSettings{
|
||||
LoginNameIncludesDomain: true,
|
||||
RequireOrgDomainVerification: true,
|
||||
SmtpSenderAddressMatchesInstanceDomain: true,
|
||||
ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE,
|
||||
}
|
||||
got := domainSettingsToPb(arg)
|
||||
allFieldsSet(t, got.ProtoReflect())
|
||||
if !proto.Equal(got, want) {
|
||||
t.Errorf("domainSettingsToPb() =\n%v\nwant\n%v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_legalSettingsToPb(t *testing.T) {
|
||||
arg := &query.PrivacyPolicy{
|
||||
TOSLink: "http://example.com/tos",
|
||||
PrivacyLink: "http://example.com/pricacy",
|
||||
HelpLink: "http://example.com/help",
|
||||
SupportEmail: "support@zitadel.com",
|
||||
IsDefault: true,
|
||||
}
|
||||
want := &settings.LegalAndSupportSettings{
|
||||
TosLink: "http://example.com/tos",
|
||||
PrivacyPolicyLink: "http://example.com/pricacy",
|
||||
HelpLink: "http://example.com/help",
|
||||
SupportEmail: "support@zitadel.com",
|
||||
ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE,
|
||||
}
|
||||
got := legalAndSupportSettingsToPb(arg)
|
||||
allFieldsSet(t, got.ProtoReflect())
|
||||
if !proto.Equal(got, want) {
|
||||
t.Errorf("legalSettingsToPb() =\n%v\nwant\n%v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_lockoutSettingsToPb(t *testing.T) {
|
||||
arg := &query.LockoutPolicy{
|
||||
MaxPasswordAttempts: 22,
|
||||
IsDefault: true,
|
||||
}
|
||||
want := &settings.LockoutSettings{
|
||||
MaxPasswordAttempts: 22,
|
||||
ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE,
|
||||
}
|
||||
got := lockoutSettingsToPb(arg)
|
||||
allFieldsSet(t, got.ProtoReflect())
|
||||
if !proto.Equal(got, want) {
|
||||
t.Errorf("lockoutSettingsToPb() =\n%v\nwant\n%v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_identityProvidersToPb(t *testing.T) {
|
||||
arg := []*query.IDPLoginPolicyLink{
|
||||
{
|
||||
IDPID: "1",
|
||||
IDPName: "foo",
|
||||
IDPType: domain.IDPTypeOIDC,
|
||||
},
|
||||
{
|
||||
IDPID: "2",
|
||||
IDPName: "bar",
|
||||
IDPType: domain.IDPTypeGitHub,
|
||||
},
|
||||
}
|
||||
want := []*settings.IdentityProvider{
|
||||
{
|
||||
Id: "1",
|
||||
Name: "foo",
|
||||
Type: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_OIDC,
|
||||
},
|
||||
{
|
||||
Id: "2",
|
||||
Name: "bar",
|
||||
Type: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITHUB,
|
||||
},
|
||||
}
|
||||
got := identityProvidersToPb(arg)
|
||||
require.Len(t, got, len(got))
|
||||
for i, v := range got {
|
||||
allFieldsSet(t, v.ProtoReflect())
|
||||
if !proto.Equal(v, want[i]) {
|
||||
t.Errorf("identityProvidersToPb() =\n%v\nwant\n%v", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_idpTypeToPb(t *testing.T) {
|
||||
type args struct {
|
||||
idpType domain.IDPType
|
||||
}
|
||||
tests := []struct {
|
||||
args args
|
||||
want settings.IdentityProviderType
|
||||
}{
|
||||
{
|
||||
args: args{domain.IDPTypeUnspecified},
|
||||
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_UNSPECIFIED,
|
||||
},
|
||||
{
|
||||
args: args{domain.IDPTypeOIDC},
|
||||
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_OIDC,
|
||||
},
|
||||
{
|
||||
args: args{domain.IDPTypeJWT},
|
||||
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_JWT,
|
||||
},
|
||||
{
|
||||
args: args{domain.IDPTypeOAuth},
|
||||
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_OAUTH,
|
||||
},
|
||||
{
|
||||
args: args{domain.IDPTypeLDAP},
|
||||
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_LDAP,
|
||||
},
|
||||
{
|
||||
args: args{domain.IDPTypeAzureAD},
|
||||
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_AZURE_AD,
|
||||
},
|
||||
{
|
||||
args: args{domain.IDPTypeGitHub},
|
||||
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITHUB,
|
||||
},
|
||||
{
|
||||
args: args{domain.IDPTypeGitHubEnterprise},
|
||||
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITHUB_ES,
|
||||
},
|
||||
{
|
||||
args: args{domain.IDPTypeGitLab},
|
||||
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITLAB,
|
||||
},
|
||||
{
|
||||
args: args{domain.IDPTypeGitLabSelfHosted},
|
||||
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITLAB_SELF_HOSTED,
|
||||
},
|
||||
{
|
||||
args: args{domain.IDPTypeGoogle},
|
||||
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GOOGLE,
|
||||
},
|
||||
{
|
||||
args: args{99},
|
||||
want: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_UNSPECIFIED,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.want.String(), func(t *testing.T) {
|
||||
if got := idpTypeToPb(tt.args.idpType); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("idpTypeToPb() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,15 +1,17 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"math"
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server/middleware"
|
||||
http_utils "github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/logstore"
|
||||
"github.com/zitadel/zitadel/internal/logstore/emitters/access"
|
||||
@ -20,6 +22,7 @@ type AccessInterceptor struct {
|
||||
svc *logstore.Service
|
||||
cookieHandler *http_utils.CookieHandler
|
||||
limitConfig *AccessConfig
|
||||
storeOnly bool
|
||||
}
|
||||
|
||||
type AccessConfig struct {
|
||||
@ -27,54 +30,85 @@ type AccessConfig struct {
|
||||
ExhaustedCookieMaxAge time.Duration
|
||||
}
|
||||
|
||||
func NewAccessInterceptor(svc *logstore.Service, cookieConfig *AccessConfig) *AccessInterceptor {
|
||||
// NewAccessInterceptor intercepts all requests and stores them to the logstore.
|
||||
// If storeOnly is false, it also checks if requests are exhausted.
|
||||
// If requests are exhausted, it also returns http.StatusTooManyRequests and sets a cookie
|
||||
func NewAccessInterceptor(svc *logstore.Service, cookieHandler *http_utils.CookieHandler, cookieConfig *AccessConfig) *AccessInterceptor {
|
||||
return &AccessInterceptor{
|
||||
svc: svc,
|
||||
cookieHandler: http_utils.NewCookieHandler(
|
||||
http_utils.WithUnsecure(),
|
||||
http_utils.WithMaxAge(int(math.Floor(cookieConfig.ExhaustedCookieMaxAge.Seconds()))),
|
||||
),
|
||||
cookieHandler: cookieHandler,
|
||||
limitConfig: cookieConfig,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AccessInterceptor) WithoutLimiting() *AccessInterceptor {
|
||||
return &AccessInterceptor{
|
||||
svc: a.svc,
|
||||
cookieHandler: a.cookieHandler,
|
||||
limitConfig: a.limitConfig,
|
||||
storeOnly: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AccessInterceptor) AccessService() *logstore.Service {
|
||||
return a.svc
|
||||
}
|
||||
|
||||
func (a *AccessInterceptor) Limit(ctx context.Context) bool {
|
||||
if !a.svc.Enabled() || a.storeOnly {
|
||||
return false
|
||||
}
|
||||
instance := authz.GetInstance(ctx)
|
||||
remaining := a.svc.Limit(ctx, instance.InstanceID())
|
||||
return remaining != nil && *remaining <= 0
|
||||
}
|
||||
|
||||
func (a *AccessInterceptor) SetExhaustedCookie(writer http.ResponseWriter, request *http.Request) {
|
||||
cookieValue := "true"
|
||||
host := request.Header.Get(middleware.HTTP1Host)
|
||||
domain := host
|
||||
if strings.ContainsAny(host, ":") {
|
||||
var err error
|
||||
domain, _, err = net.SplitHostPort(host)
|
||||
if err != nil {
|
||||
logging.WithError(err).WithField("host", host).Warning("failed to extract cookie domain from request host")
|
||||
}
|
||||
}
|
||||
a.cookieHandler.SetCookie(writer, a.limitConfig.ExhaustedCookieKey, domain, cookieValue)
|
||||
}
|
||||
|
||||
func (a *AccessInterceptor) DeleteExhaustedCookie(writer http.ResponseWriter, request *http.Request) {
|
||||
a.cookieHandler.DeleteCookie(writer, request, a.limitConfig.ExhaustedCookieKey)
|
||||
}
|
||||
|
||||
func (a *AccessInterceptor) Handle(next http.Handler) http.Handler {
|
||||
if !a.svc.Enabled() {
|
||||
return next
|
||||
}
|
||||
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
ctx := request.Context()
|
||||
var err error
|
||||
|
||||
tracingCtx, checkSpan := tracing.NewNamedSpan(ctx, "checkAccess")
|
||||
|
||||
wrappedWriter := &statusRecorder{ResponseWriter: writer, status: 0}
|
||||
|
||||
instance := authz.GetInstance(ctx)
|
||||
remaining := a.svc.Limit(tracingCtx, instance.InstanceID())
|
||||
limit := remaining != nil && *remaining == 0
|
||||
|
||||
a.cookieHandler.SetCookie(wrappedWriter, a.limitConfig.ExhaustedCookieKey, request.Host, strconv.FormatBool(limit))
|
||||
|
||||
if limit {
|
||||
wrappedWriter.WriteHeader(http.StatusTooManyRequests)
|
||||
wrappedWriter.ignoreWrites = true
|
||||
}
|
||||
|
||||
limited := a.Limit(tracingCtx)
|
||||
checkSpan.End()
|
||||
|
||||
if limited {
|
||||
a.SetExhaustedCookie(wrappedWriter, request)
|
||||
http.Error(wrappedWriter, "quota for authenticated requests is exhausted", http.StatusTooManyRequests)
|
||||
}
|
||||
if !limited && !a.storeOnly {
|
||||
a.DeleteExhaustedCookie(wrappedWriter, request)
|
||||
}
|
||||
if !limited {
|
||||
next.ServeHTTP(wrappedWriter, request)
|
||||
|
||||
}
|
||||
tracingCtx, writeSpan := tracing.NewNamedSpan(tracingCtx, "writeAccess")
|
||||
defer writeSpan.End()
|
||||
|
||||
requestURL := request.RequestURI
|
||||
unescapedURL, err := url.QueryUnescape(requestURL)
|
||||
if err != nil {
|
||||
logging.WithError(err).WithField("url", requestURL).Warning("failed to unescape request url")
|
||||
// err = nil is effective because of deferred tracing span end
|
||||
err = nil
|
||||
}
|
||||
instance := authz.GetInstance(tracingCtx)
|
||||
a.svc.Handle(tracingCtx, &access.Record{
|
||||
LogDate: time.Now(),
|
||||
Protocol: access.HTTP,
|
||||
|
@ -1,9 +1,11 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
@ -24,6 +26,7 @@ import (
|
||||
type Config struct {
|
||||
ShortCache middleware.CacheConfig
|
||||
LongCache middleware.CacheConfig
|
||||
InstanceManagementURL string
|
||||
}
|
||||
|
||||
type spaHandler struct {
|
||||
@ -88,7 +91,7 @@ func (f *file) Stat() (_ fs.FileInfo, err error) {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, callDurationInterceptor, instanceHandler, accessInterceptor func(http.Handler) http.Handler, customerPortal string) (http.Handler, error) {
|
||||
func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, callDurationInterceptor, instanceHandler func(http.Handler) http.Handler, limitingAccessInterceptor *middleware.AccessInterceptor, customerPortal string) (http.Handler, error) {
|
||||
fSys, err := fs.Sub(static, "static")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -103,14 +106,26 @@ func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, call
|
||||
|
||||
handler := mux.NewRouter()
|
||||
|
||||
handler.Use(callDurationInterceptor, instanceHandler, security, accessInterceptor)
|
||||
handler.Use(callDurationInterceptor, instanceHandler, security, limitingAccessInterceptor.WithoutLimiting().Handle)
|
||||
handler.Handle(envRequestPath, middleware.TelemetryHandler()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
url := http_util.BuildOrigin(r.Host, externalSecure)
|
||||
environmentJSON, err := createEnvironmentJSON(url, issuer(r), authz.GetInstance(r.Context()).ConsoleClientID(), customerPortal)
|
||||
ctx := r.Context()
|
||||
instance := authz.GetInstance(ctx)
|
||||
instanceMgmtURL, err := templateInstanceManagementURL(config.InstanceManagementURL, instance)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("unable to template instance management url for console: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
environmentJSON, err := createEnvironmentJSON(url, issuer(r), instance.ConsoleClientID(), customerPortal, instanceMgmtURL)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("unable to marshal env for console: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if limitingAccessInterceptor.Limit(ctx) {
|
||||
limitingAccessInterceptor.SetExhaustedCookie(w, r)
|
||||
} else {
|
||||
limitingAccessInterceptor.DeleteExhaustedCookie(w, r)
|
||||
}
|
||||
_, err = w.Write(environmentJSON)
|
||||
logging.OnError(err).Error("error serving environment.json")
|
||||
})))
|
||||
@ -118,6 +133,18 @@ func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, call
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
func templateInstanceManagementURL(templateableCookieValue string, instance authz.Instance) (string, error) {
|
||||
cookieValueTemplate, err := template.New("cookievalue").Parse(templateableCookieValue)
|
||||
if err != nil {
|
||||
return templateableCookieValue, err
|
||||
}
|
||||
cookieValue := new(bytes.Buffer)
|
||||
if err = cookieValueTemplate.Execute(cookieValue, instance); err != nil {
|
||||
return templateableCookieValue, err
|
||||
}
|
||||
return cookieValue.String(), nil
|
||||
}
|
||||
|
||||
func csp() *middleware.CSP {
|
||||
csp := middleware.DefaultSCP
|
||||
csp.StyleSrc = csp.StyleSrc.AddInline()
|
||||
@ -127,17 +154,19 @@ func csp() *middleware.CSP {
|
||||
return &csp
|
||||
}
|
||||
|
||||
func createEnvironmentJSON(api, issuer, clientID, customerPortal string) ([]byte, error) {
|
||||
func createEnvironmentJSON(api, issuer, clientID, customerPortal, instanceMgmtUrl string) ([]byte, error) {
|
||||
environment := struct {
|
||||
API string `json:"api,omitempty"`
|
||||
Issuer string `json:"issuer,omitempty"`
|
||||
ClientID string `json:"clientid,omitempty"`
|
||||
CustomerPortal string `json:"customer_portal,omitempty"`
|
||||
InstanceManagementURL string `json:"instance_management_url,omitempty"`
|
||||
}{
|
||||
API: api,
|
||||
Issuer: issuer,
|
||||
ClientID: clientID,
|
||||
CustomerPortal: customerPortal,
|
||||
InstanceManagementURL: instanceMgmtUrl,
|
||||
}
|
||||
return json.Marshal(environment)
|
||||
}
|
||||
|
@ -327,7 +327,12 @@ func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq *
|
||||
func (l *Login) renderInternalError(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) {
|
||||
var msg string
|
||||
if err != nil {
|
||||
logging.WithError(err).WithField("auth_req_id", authReq.ID).Error()
|
||||
log := logging.WithError(err)
|
||||
if authReq != nil {
|
||||
log = log.WithField("auth_req_id", authReq.ID)
|
||||
}
|
||||
log.Error()
|
||||
|
||||
_, msg = l.getErrorMessage(r, err)
|
||||
}
|
||||
data := l.getBaseData(r, authReq, "Errors.Internal", "", "Internal", msg)
|
||||
|
@ -1,5 +1,7 @@
|
||||
package domain
|
||||
|
||||
import "github.com/zitadel/logging"
|
||||
|
||||
type IDPState int32
|
||||
|
||||
const (
|
||||
@ -56,3 +58,36 @@ func (t IDPType) GetCSSClass() string {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func IDPName(name string, idpType IDPType) string {
|
||||
if name != "" {
|
||||
return name
|
||||
}
|
||||
return idpType.DisplayName()
|
||||
}
|
||||
|
||||
// DisplayName returns the name or a default
|
||||
// to be used when always a name must be displayed (e.g. login)
|
||||
func (t IDPType) DisplayName() string {
|
||||
switch t {
|
||||
case IDPTypeGitHub:
|
||||
return "GitHub"
|
||||
case IDPTypeGitLab:
|
||||
return "GitLab"
|
||||
case IDPTypeGoogle:
|
||||
return "Google"
|
||||
case IDPTypeUnspecified,
|
||||
IDPTypeOIDC,
|
||||
IDPTypeJWT,
|
||||
IDPTypeOAuth,
|
||||
IDPTypeLDAP,
|
||||
IDPTypeAzureAD,
|
||||
IDPTypeGitHubEnterprise,
|
||||
IDPTypeGitLabSelfHosted:
|
||||
fallthrough
|
||||
default:
|
||||
// we should never get here, so log it
|
||||
logging.Errorf("name of provider (type %d) is empty", t)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,6 @@ import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
)
|
||||
|
||||
@ -70,30 +68,7 @@ func (p IDPProvider) IsValid() bool {
|
||||
// DisplayName returns the name or a default
|
||||
// to be used when always a name must be displayed (e.g. login)
|
||||
func (p IDPProvider) DisplayName() string {
|
||||
if p.Name != "" {
|
||||
return p.Name
|
||||
}
|
||||
switch p.IDPType {
|
||||
case IDPTypeGitHub:
|
||||
return "GitHub"
|
||||
case IDPTypeGitLab:
|
||||
return "GitLab"
|
||||
case IDPTypeGoogle:
|
||||
return "Google"
|
||||
case IDPTypeUnspecified,
|
||||
IDPTypeOIDC,
|
||||
IDPTypeJWT,
|
||||
IDPTypeOAuth,
|
||||
IDPTypeLDAP,
|
||||
IDPTypeAzureAD,
|
||||
IDPTypeGitHubEnterprise,
|
||||
IDPTypeGitLabSelfHosted:
|
||||
fallthrough
|
||||
default:
|
||||
// we should never get here, so log it
|
||||
logging.Errorf("name of provider (type %d) is empty - id: %s", p.IDPType, p.IDPConfigID)
|
||||
return ""
|
||||
}
|
||||
return IDPName(p.Name, p.IDPType)
|
||||
}
|
||||
|
||||
type PasswordlessType int32
|
||||
|
@ -80,27 +80,33 @@ var (
|
||||
name: projection.IDPLoginPolicyLinkOwnerRemovedCol,
|
||||
table: idpLoginPolicyLinkTable,
|
||||
}
|
||||
|
||||
idpLoginPolicyOwnerTable = loginPolicyTable.setAlias("login_policy_owner")
|
||||
idpLoginPolicyOwnerIDCol = LoginPolicyColumnOrgID.setTable(idpLoginPolicyOwnerTable)
|
||||
idpLoginPolicyOwnerInstanceIDCol = LoginPolicyColumnInstanceID.setTable(idpLoginPolicyOwnerTable)
|
||||
idpLoginPolicyOwnerIsDefaultCol = LoginPolicyColumnIsDefault.setTable(idpLoginPolicyOwnerTable)
|
||||
idpLoginPolicyOwnerOwnerRemovedCol = LoginPolicyColumnOwnerRemoved.setTable(idpLoginPolicyOwnerTable)
|
||||
)
|
||||
|
||||
func (q *Queries) IDPLoginPolicyLinks(ctx context.Context, resourceOwner string, queries *IDPLoginPolicyLinksSearchQuery, withOwnerRemoved bool) (idps *IDPLoginPolicyLinks, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
query, scan := prepareIDPLoginPolicyLinksQuery(ctx, q.client)
|
||||
query, scan := prepareIDPLoginPolicyLinksQuery(ctx, q.client, resourceOwner)
|
||||
eq := sq.Eq{
|
||||
IDPLoginPolicyLinkResourceOwnerCol.identifier(): resourceOwner,
|
||||
IDPLoginPolicyLinkInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
}
|
||||
if !withOwnerRemoved {
|
||||
eq[IDPLoginPolicyLinkOwnerRemovedCol.identifier()] = false
|
||||
eq[idpLoginPolicyOwnerOwnerRemovedCol.identifier()] = false
|
||||
}
|
||||
|
||||
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInvalidArgument(err, "QUERY-FDbKW", "Errors.Query.InvalidRequest")
|
||||
}
|
||||
|
||||
rows, err := q.client.QueryContext(ctx, stmt, args...)
|
||||
if err != nil {
|
||||
if err != nil || rows.Err() != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-ZkKUc", "Errors.Internal")
|
||||
}
|
||||
idps, err = scan(rows)
|
||||
@ -111,7 +117,11 @@ func (q *Queries) IDPLoginPolicyLinks(ctx context.Context, resourceOwner string,
|
||||
return idps, err
|
||||
}
|
||||
|
||||
func prepareIDPLoginPolicyLinksQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*IDPLoginPolicyLinks, error)) {
|
||||
func prepareIDPLoginPolicyLinksQuery(ctx context.Context, db prepareDatabase, resourceOwner string) (sq.SelectBuilder, func(*sql.Rows) (*IDPLoginPolicyLinks, error)) {
|
||||
resourceOwnerQuery, resourceOwnerArgs, err := prepareIDPLoginPolicyLinksResourceOwnerQuery(ctx, resourceOwner)
|
||||
if err != nil {
|
||||
return sq.SelectBuilder{}, nil
|
||||
}
|
||||
return sq.Select(
|
||||
IDPLoginPolicyLinkIDPIDCol.identifier(),
|
||||
IDPTemplateNameCol.identifier(),
|
||||
@ -119,7 +129,12 @@ func prepareIDPLoginPolicyLinksQuery(ctx context.Context, db prepareDatabase) (s
|
||||
IDPTemplateOwnerTypeCol.identifier(),
|
||||
countColumn.identifier()).
|
||||
From(idpLoginPolicyLinkTable.identifier()).
|
||||
LeftJoin(join(IDPTemplateIDCol, IDPLoginPolicyLinkIDPIDCol) + db.Timetravel(call.Took(ctx))).
|
||||
LeftJoin(join(IDPTemplateIDCol, IDPLoginPolicyLinkIDPIDCol)).
|
||||
RightJoin("("+resourceOwnerQuery+") AS "+idpLoginPolicyOwnerTable.alias+" ON "+
|
||||
idpLoginPolicyOwnerIDCol.identifier()+" = "+IDPLoginPolicyLinkResourceOwnerCol.identifier()+" AND "+
|
||||
idpLoginPolicyOwnerInstanceIDCol.identifier()+" = "+IDPLoginPolicyLinkInstanceIDCol.identifier()+
|
||||
" "+db.Timetravel(call.Took(ctx)),
|
||||
resourceOwnerArgs...).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(rows *sql.Rows) (*IDPLoginPolicyLinks, error) {
|
||||
links := make([]*IDPLoginPolicyLink, 0)
|
||||
@ -164,3 +179,22 @@ func prepareIDPLoginPolicyLinksQuery(ctx context.Context, db prepareDatabase) (s
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func prepareIDPLoginPolicyLinksResourceOwnerQuery(ctx context.Context, resourceOwner string) (string, []interface{}, error) {
|
||||
eqPolicy := sq.Eq{idpLoginPolicyOwnerInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID()}
|
||||
return sq.Select(
|
||||
idpLoginPolicyOwnerIDCol.identifier(),
|
||||
idpLoginPolicyOwnerInstanceIDCol.identifier(),
|
||||
idpLoginPolicyOwnerOwnerRemovedCol.identifier(),
|
||||
).
|
||||
From(idpLoginPolicyOwnerTable.identifier()).
|
||||
Where(
|
||||
sq.And{
|
||||
eqPolicy,
|
||||
sq.Or{
|
||||
sq.Eq{idpLoginPolicyOwnerIDCol.identifier(): resourceOwner},
|
||||
sq.Eq{idpLoginPolicyOwnerIDCol.identifier(): authz.GetInstance(ctx).InstanceID()},
|
||||
},
|
||||
}).
|
||||
Limit(1).OrderBy(idpLoginPolicyOwnerIsDefaultCol.identifier()).ToSql()
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
@ -8,6 +9,8 @@ import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
)
|
||||
|
||||
@ -19,6 +22,9 @@ var (
|
||||
` COUNT(*) OVER ()` +
|
||||
` FROM projections.idp_login_policy_links5` +
|
||||
` LEFT JOIN projections.idp_templates5 ON projections.idp_login_policy_links5.idp_id = projections.idp_templates5.id AND projections.idp_login_policy_links5.instance_id = projections.idp_templates5.instance_id` +
|
||||
` RIGHT JOIN (SELECT login_policy_owner.aggregate_id, login_policy_owner.instance_id, login_policy_owner.owner_removed FROM projections.login_policies4 AS login_policy_owner` +
|
||||
` WHERE (login_policy_owner.instance_id = $1 AND (login_policy_owner.aggregate_id = $2 OR login_policy_owner.aggregate_id = $3)) ORDER BY login_policy_owner.is_default LIMIT 1) AS login_policy_owner` +
|
||||
` ON login_policy_owner.aggregate_id = projections.idp_login_policy_links5.resource_owner AND login_policy_owner.instance_id = projections.idp_login_policy_links5.instance_id` +
|
||||
` AS OF SYSTEM TIME '-1 ms'`)
|
||||
loginPolicyIDPLinksCols = []string{
|
||||
"idp_id",
|
||||
@ -42,7 +48,9 @@ func Test_IDPLoginPolicyLinkPrepares(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "prepareIDPsQuery found",
|
||||
prepare: prepareIDPLoginPolicyLinksQuery,
|
||||
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*IDPLoginPolicyLinks, error)) {
|
||||
return prepareIDPLoginPolicyLinksQuery(ctx, db, "resourceOwner")
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
loginPolicyIDPLinksQuery,
|
||||
@ -73,7 +81,9 @@ func Test_IDPLoginPolicyLinkPrepares(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "prepareIDPsQuery no idp",
|
||||
prepare: prepareIDPLoginPolicyLinksQuery,
|
||||
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*IDPLoginPolicyLinks, error)) {
|
||||
return prepareIDPLoginPolicyLinksQuery(ctx, db, "resourceOwner")
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
loginPolicyIDPLinksQuery,
|
||||
@ -103,7 +113,9 @@ func Test_IDPLoginPolicyLinkPrepares(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "prepareIDPsQuery sql err",
|
||||
prepare: prepareIDPLoginPolicyLinksQuery,
|
||||
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*IDPLoginPolicyLinks, error)) {
|
||||
return prepareIDPLoginPolicyLinksQuery(ctx, db, "resourceOwner")
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQueryErr(
|
||||
loginPolicyIDPLinksQuery,
|
||||
|
@ -15,6 +15,13 @@ message Organisation {
|
||||
}
|
||||
}
|
||||
|
||||
message RequestContext {
|
||||
oneof resource_owner {
|
||||
string org_id = 1;
|
||||
bool instance = 2 [(validate.rules).bool = {const: true}];
|
||||
}
|
||||
}
|
||||
|
||||
message ListQuery {
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
|
||||
json_schema: {
|
||||
|
81
proto/zitadel/settings/v2alpha/branding_settings.proto
Normal file
81
proto/zitadel/settings/v2alpha/branding_settings.proto
Normal file
@ -0,0 +1,81 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package zitadel.settings.v2alpha;
|
||||
|
||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2alpha;settings";
|
||||
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
import "zitadel/settings/v2alpha/settings.proto";
|
||||
|
||||
message BrandingSettings {
|
||||
Theme light_theme = 1;
|
||||
Theme dark_theme = 2;
|
||||
string font_url = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "url to the font used";
|
||||
example: "\"https://acme.com/assets/v1/165617850692654601/policy/label/font-180950243237405441\"";
|
||||
}
|
||||
];
|
||||
// hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set
|
||||
bool hide_login_name_suffix = 4 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "hides the org suffix on the login form if the scope \"urn:zitadel:iam:org:domain:primary:{domainname}\" is set";
|
||||
}
|
||||
];
|
||||
bool disable_watermark = 5 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "boolean to disable the watermark";
|
||||
}
|
||||
];
|
||||
// resource_owner_type returns if the setting is managed on the organization or on the instance
|
||||
ResourceOwnerType resource_owner_type = 6 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "resource_owner_type returns if the setting is managed on the organization or on the instance";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message Theme {
|
||||
// hex value for primary color
|
||||
string primary_color = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "hex value for primary color";
|
||||
example: "\"#5469d4\"";
|
||||
}
|
||||
];
|
||||
// hex value for background color
|
||||
string background_color = 2 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "hex value for background color";
|
||||
example: "\"#FAFAFA\"";
|
||||
}
|
||||
];
|
||||
// hex value for warning color
|
||||
string warn_color = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "hex value for warn color";
|
||||
example: "\"#CD3D56\"";
|
||||
}
|
||||
];
|
||||
// hex value for font color
|
||||
string font_color = 4 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "hex value for font color";
|
||||
example: "\"#000000\"";
|
||||
}
|
||||
];
|
||||
// url where the logo is served
|
||||
string logo_url = 5 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "url to the logo";
|
||||
example: "\"https://acme.com/assets/v1/165617850692654601/policy/label/logo-180950416321494657\"";
|
||||
}
|
||||
];
|
||||
// url where the icon is served
|
||||
string icon_url = 6 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "url to the icon";
|
||||
example: "\"https://acme.com/assets/v1/165617850692654601/policy/label/icon-180950498874178817\"";
|
||||
}
|
||||
];
|
||||
}
|
33
proto/zitadel/settings/v2alpha/domain_settings.proto
Normal file
33
proto/zitadel/settings/v2alpha/domain_settings.proto
Normal file
@ -0,0 +1,33 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package zitadel.settings.v2alpha;
|
||||
|
||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2alpha;settings";
|
||||
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
import "zitadel/settings/v2alpha/settings.proto";
|
||||
|
||||
message DomainSettings {
|
||||
bool login_name_includes_domain = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "the username has to end with the domain of its organization"
|
||||
}
|
||||
];
|
||||
bool require_org_domain_verification = 2 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "defines if organization domains should be verified upon creation, otherwise will be created already verified"
|
||||
}
|
||||
];
|
||||
bool smtp_sender_address_matches_instance_domain = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "defines if the SMTP sender address domain should match an existing domain on the instance"
|
||||
}
|
||||
];
|
||||
// resource_owner_type returns if the setting is managed on the organization or on the instance
|
||||
ResourceOwnerType resource_owner_type = 6 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "resource_owner_type returns if the setting is managed on the organization or on the instance";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
40
proto/zitadel/settings/v2alpha/legal_settings.proto
Normal file
40
proto/zitadel/settings/v2alpha/legal_settings.proto
Normal file
@ -0,0 +1,40 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package zitadel.settings.v2alpha;
|
||||
|
||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2alpha;settings";
|
||||
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
import "zitadel/settings/v2alpha/settings.proto";
|
||||
import "validate/validate.proto";
|
||||
|
||||
message LegalAndSupportSettings {
|
||||
string tos_link = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://zitadel.com/docs/legal/terms-of-service\"";
|
||||
}
|
||||
];
|
||||
string privacy_policy_link = 2 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://zitadel.com/docs/legal/privacy-policy\"";
|
||||
}
|
||||
];
|
||||
string help_link = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://zitadel.com/docs/manuals/introduction\"";
|
||||
}
|
||||
];
|
||||
string support_email = 4 [
|
||||
(validate.rules).string = {ignore_empty: true, max_len: 320, email: true},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"support-email@test.com\"";
|
||||
description: "help / support email address."
|
||||
}
|
||||
];
|
||||
// resource_owner_type returns if the setting is managed on the organization or on the instance
|
||||
ResourceOwnerType resource_owner_type = 5 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "resource_owner_type returns if the setting is managed on the organization or on the instance";
|
||||
}
|
||||
];
|
||||
}
|
23
proto/zitadel/settings/v2alpha/lockout_settings.proto
Normal file
23
proto/zitadel/settings/v2alpha/lockout_settings.proto
Normal file
@ -0,0 +1,23 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package zitadel.settings.v2alpha;
|
||||
|
||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2alpha;settings";
|
||||
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
import "zitadel/settings/v2alpha/settings.proto";
|
||||
|
||||
message LockoutSettings {
|
||||
uint64 max_password_attempts = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Maximum password check attempts before the account gets locked. Attempts are reset as soon as the password is entered correctly or the password is reset. If set to 0 the account will never be locked."
|
||||
example: "\"10\""
|
||||
}
|
||||
];
|
||||
// resource_owner_type returns if the settings is managed on the organization or on the instance
|
||||
ResourceOwnerType resource_owner_type = 2 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "resource_owner_type returns if the settings is managed on the organization or on the instance";
|
||||
}
|
||||
];
|
||||
}
|
143
proto/zitadel/settings/v2alpha/login_settings.proto
Normal file
143
proto/zitadel/settings/v2alpha/login_settings.proto
Normal file
@ -0,0 +1,143 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package zitadel.settings.v2alpha;
|
||||
|
||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2alpha;settings";
|
||||
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
import "zitadel/settings/v2alpha/settings.proto";
|
||||
import "google/protobuf/duration.proto";
|
||||
|
||||
message LoginSettings {
|
||||
bool allow_username_password = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "defines if a user is allowed to log in with his username and password";
|
||||
}
|
||||
];
|
||||
bool allow_register = 2 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "defines if a person is allowed to register a user on this organization";
|
||||
}
|
||||
];
|
||||
bool allow_external_idp = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "defines if a user is allowed to add a defined identity provider. E.g. Google auth";
|
||||
}
|
||||
];
|
||||
bool force_mfa = 4 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "defines if a user MUST use a multi-factor to log in";
|
||||
}
|
||||
];
|
||||
PasskeysType passkeys_type = 5 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "defines if passkeys are allowed for users"
|
||||
}
|
||||
];
|
||||
bool hide_password_reset = 6 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "defines if password reset link should be shown in the login screen"
|
||||
}
|
||||
];
|
||||
bool ignore_unknown_usernames = 7 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "defines if unknown username on login screen directly returns an error or always displays the password screen"
|
||||
}
|
||||
];
|
||||
string default_redirect_uri = 8 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "defines where the user will be redirected to if the login is started without app context (e.g. from mail)";
|
||||
example: "\"https://acme.com/ui/console\"";
|
||||
}
|
||||
];
|
||||
google.protobuf.Duration password_check_lifetime = 9 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Defines after how much time the user has to re-authenticate with the password.";
|
||||
example: "\"864000s\"";
|
||||
}
|
||||
];
|
||||
google.protobuf.Duration external_login_check_lifetime = 10 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Defines after how much time the user has to re-authenticate with an external provider.";
|
||||
example: "\"864000s\"";
|
||||
}
|
||||
];
|
||||
google.protobuf.Duration mfa_init_skip_lifetime = 11 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Defines after how much time the mfa prompt will be shown again.";
|
||||
example: "\"2592000s\"";
|
||||
}
|
||||
];
|
||||
google.protobuf.Duration second_factor_check_lifetime = 12 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Defines after how long the second-factor check is valid.";
|
||||
example: "\"64800s\"";
|
||||
}
|
||||
];
|
||||
google.protobuf.Duration multi_factor_check_lifetime = 13 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Defines how long the multi-factor check is valid.";
|
||||
example: "\"43200s\"";
|
||||
}
|
||||
];
|
||||
repeated SecondFactorType second_factors = 14;
|
||||
repeated MultiFactorType multi_factors = 15;
|
||||
// If set to true, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the org domains and will redirect to the registration of that organization on success.
|
||||
bool allow_domain_discovery = 16 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "If set to true, the suffix (@domain.com) of an unknown username input on the login screen will be matched against the org domains and will redirect to the registration of that organization on success."
|
||||
}
|
||||
];
|
||||
bool disable_login_with_email = 17 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "defines if the user can additionally (to the login name) be identified by their verified email address"
|
||||
}
|
||||
];
|
||||
bool disable_login_with_phone = 18 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "defines if the user can additionally (to the login name) be identified by their verified phone number"
|
||||
}
|
||||
];
|
||||
// resource_owner_type returns if the settings is managed on the organization or on the instance
|
||||
ResourceOwnerType resource_owner_type = 19 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "resource_owner_type returns if the settings is managed on the organization or on the instance";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
enum SecondFactorType {
|
||||
SECOND_FACTOR_TYPE_UNSPECIFIED = 0;
|
||||
SECOND_FACTOR_TYPE_OTP = 1;
|
||||
SECOND_FACTOR_TYPE_U2F = 2;
|
||||
}
|
||||
|
||||
enum MultiFactorType {
|
||||
MULTI_FACTOR_TYPE_UNSPECIFIED = 0;
|
||||
MULTI_FACTOR_TYPE_U2F_WITH_VERIFICATION = 1;
|
||||
}
|
||||
|
||||
enum PasskeysType {
|
||||
PASSKEYS_TYPE_NOT_ALLOWED = 0;
|
||||
PASSKEYS_TYPE_ALLOWED = 1;
|
||||
}
|
||||
|
||||
message IdentityProvider {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
IdentityProviderType type = 3;
|
||||
}
|
||||
|
||||
enum IdentityProviderType {
|
||||
IDENTITY_PROVIDER_TYPE_UNSPECIFIED = 0;
|
||||
IDENTITY_PROVIDER_TYPE_OIDC = 1;
|
||||
IDENTITY_PROVIDER_TYPE_JWT = 2;
|
||||
IDENTITY_PROVIDER_TYPE_LDAP = 3;
|
||||
IDENTITY_PROVIDER_TYPE_OAUTH = 4;
|
||||
IDENTITY_PROVIDER_TYPE_AZURE_AD = 5;
|
||||
IDENTITY_PROVIDER_TYPE_GITHUB = 6;
|
||||
IDENTITY_PROVIDER_TYPE_GITHUB_ES = 7;
|
||||
IDENTITY_PROVIDER_TYPE_GITLAB = 8;
|
||||
IDENTITY_PROVIDER_TYPE_GITLAB_SELF_HOSTED = 9;
|
||||
IDENTITY_PROVIDER_TYPE_GOOGLE = 10;
|
||||
}
|
43
proto/zitadel/settings/v2alpha/password_settings.proto
Normal file
43
proto/zitadel/settings/v2alpha/password_settings.proto
Normal file
@ -0,0 +1,43 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package zitadel.settings.v2alpha;
|
||||
|
||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2alpha;settings";
|
||||
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
import "zitadel/settings/v2alpha/settings.proto";
|
||||
|
||||
message PasswordComplexitySettings {
|
||||
uint64 min_length = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Defines the minimum length of a password.";
|
||||
example: "\"8\""
|
||||
}
|
||||
];
|
||||
bool requires_uppercase = 2 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "defines if the password MUST contain an upper case letter"
|
||||
}
|
||||
];
|
||||
bool requires_lowercase = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "defines if the password MUST contain a lowercase letter"
|
||||
}
|
||||
];
|
||||
bool requires_number = 4 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "defines if the password MUST contain a number"
|
||||
}
|
||||
];
|
||||
bool requires_symbol = 5 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "defines if the password MUST contain a symbol. E.g. \"$\""
|
||||
}
|
||||
];
|
||||
// resource_owner_type returns if the settings is managed on the organization or on the instance
|
||||
ResourceOwnerType resource_owner_type = 6 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "resource_owner_type returns if the settings is managed on the organization or on the instance";
|
||||
}
|
||||
];
|
||||
}
|
13
proto/zitadel/settings/v2alpha/settings.proto
Normal file
13
proto/zitadel/settings/v2alpha/settings.proto
Normal file
@ -0,0 +1,13 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package zitadel.settings.v2alpha;
|
||||
|
||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2alpha;settings";
|
||||
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
|
||||
enum ResourceOwnerType {
|
||||
RESOURCE_OWNER_TYPE_UNSPECIFIED = 0;
|
||||
RESOURCE_OWNER_TYPE_INSTANCE = 1;
|
||||
RESOURCE_OWNER_TYPE_ORG = 2;
|
||||
}
|
356
proto/zitadel/settings/v2alpha/settings_service.proto
Normal file
356
proto/zitadel/settings/v2alpha/settings_service.proto
Normal file
@ -0,0 +1,356 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package zitadel.settings.v2alpha;
|
||||
|
||||
import "zitadel/protoc_gen_zitadel/v2/options.proto";
|
||||
import "zitadel/object/v2alpha/object.proto";
|
||||
import "zitadel/settings/v2alpha/branding_settings.proto";
|
||||
import "zitadel/settings/v2alpha/domain_settings.proto";
|
||||
import "zitadel/settings/v2alpha/legal_settings.proto";
|
||||
import "zitadel/settings/v2alpha/lockout_settings.proto";
|
||||
import "zitadel/settings/v2alpha/login_settings.proto";
|
||||
import "zitadel/settings/v2alpha/password_settings.proto";
|
||||
import "google/api/annotations.proto";
|
||||
import "google/api/field_behavior.proto";
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
import "validate/validate.proto";
|
||||
|
||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2alpha;settings";
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
||||
info: {
|
||||
title: "Settings Service";
|
||||
version: "2.0-alpha";
|
||||
description: "This API is intended to manage settings in a ZITADEL instance. This project is in alpha state. It can AND will continue breaking until the services provide the same functionality as the current login.";
|
||||
contact:{
|
||||
name: "ZITADEL"
|
||||
url: "https://zitadel.com"
|
||||
email: "hi@zitadel.com"
|
||||
}
|
||||
license: {
|
||||
name: "Apache 2.0",
|
||||
url: "https://github.com/zitadel/zitadel/blob/main/LICENSE";
|
||||
};
|
||||
};
|
||||
schemes: HTTPS;
|
||||
schemes: HTTP;
|
||||
|
||||
consumes: "application/json";
|
||||
consumes: "application/grpc";
|
||||
|
||||
produces: "application/json";
|
||||
produces: "application/grpc";
|
||||
|
||||
consumes: "application/grpc-web+proto";
|
||||
produces: "application/grpc-web+proto";
|
||||
|
||||
host: "$ZITADEL_DOMAIN";
|
||||
base_path: "/";
|
||||
|
||||
external_docs: {
|
||||
description: "Detailed information about ZITADEL",
|
||||
url: "https://zitadel.com/docs"
|
||||
}
|
||||
|
||||
responses: {
|
||||
key: "403";
|
||||
value: {
|
||||
description: "Returned when the user does not have permission to access the resource.";
|
||||
schema: {
|
||||
json_schema: {
|
||||
ref: "#/definitions/rpcStatus";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
responses: {
|
||||
key: "404";
|
||||
value: {
|
||||
description: "Returned when the resource does not exist.";
|
||||
schema: {
|
||||
json_schema: {
|
||||
ref: "#/definitions/rpcStatus";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
service SettingsService {
|
||||
|
||||
// Get basic information over the instance
|
||||
rpc GetGeneralSettings (GetGeneralSettingsRequest) returns (GetGeneralSettingsResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v2alpha/settings"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "policy.read"
|
||||
}
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
summary: "Get basic information over the instance";
|
||||
description: "Return the basic information of the instance for the requested context"
|
||||
responses: {
|
||||
key: "200"
|
||||
value: {
|
||||
description: "OK";
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Get the login settings
|
||||
rpc GetLoginSettings (GetLoginSettingsRequest) returns (GetLoginSettingsResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v2alpha/settings/login"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "policy.read"
|
||||
}
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
summary: "Get the login settings";
|
||||
description: "Return the settings for the requested context"
|
||||
responses: {
|
||||
key: "200"
|
||||
value: {
|
||||
description: "OK";
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Get the current active identity providers
|
||||
rpc GetActiveIdentityProviders (GetActiveIdentityProvidersRequest) returns (GetActiveIdentityProvidersResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v2alpha/settings/login/idps"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "policy.read"
|
||||
}
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
summary: "Get the current active identity providers";
|
||||
description: "Return the current active identity providers for the requested context"
|
||||
responses: {
|
||||
key: "200"
|
||||
value: {
|
||||
description: "OK";
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Get the password complexity settings
|
||||
rpc GetPasswordComplexitySettings (GetPasswordComplexitySettingsRequest) returns (GetPasswordComplexitySettingsResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v2alpha/settings/password/complexity"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "policy.read"
|
||||
}
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
summary: "Get the password complexity settings";
|
||||
description: "Return the password complexity settings for the requested context"
|
||||
responses: {
|
||||
key: "200"
|
||||
value: {
|
||||
description: "OK";
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Get the current active branding settings
|
||||
rpc GetBrandingSettings (GetBrandingSettingsRequest) returns (GetBrandingSettingsResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v2alpha/settings/branding"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "policy.read"
|
||||
}
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
summary: "Get the current active branding settings";
|
||||
description: "Return the current active branding settings for the requested context"
|
||||
responses: {
|
||||
key: "200"
|
||||
value: {
|
||||
description: "OK";
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Get the domain settings
|
||||
rpc GetDomainSettings (GetDomainSettingsRequest) returns (GetDomainSettingsResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v2alpha/settings/domain"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "policy.read"
|
||||
}
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
summary: "Get the domain settings";
|
||||
description: "Return the domain settings for the requested context"
|
||||
responses: {
|
||||
key: "200"
|
||||
value: {
|
||||
description: "OK";
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Get the legal and support settings
|
||||
rpc GetLegalAndSupportSettings (GetLegalAndSupportSettingsRequest) returns (GetLegalAndSupportSettingsResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v2alpha/settings/legal_support"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "policy.read"
|
||||
}
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
summary: "Get the legal and support settings";
|
||||
description: "Return the legal settings for the requested context"
|
||||
responses: {
|
||||
key: "200"
|
||||
value: {
|
||||
description: "OK";
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Get the lockout settings
|
||||
rpc GetLockoutSettings (GetLockoutSettingsRequest) returns (GetLockoutSettingsResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v2alpha/settings/lockout"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "policy.read"
|
||||
}
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
summary: "Get the lockout settings";
|
||||
description: "Return the lockout settings for the requested context, which define when a user will be locked"
|
||||
responses: {
|
||||
key: "200"
|
||||
value: {
|
||||
description: "OK";
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
message GetLoginSettingsRequest {
|
||||
zitadel.object.v2alpha.RequestContext ctx = 1;
|
||||
}
|
||||
|
||||
message GetLoginSettingsResponse {
|
||||
zitadel.object.v2alpha.Details details = 1;
|
||||
zitadel.settings.v2alpha.LoginSettings settings = 2;
|
||||
}
|
||||
|
||||
message GetPasswordComplexitySettingsRequest {
|
||||
zitadel.object.v2alpha.RequestContext ctx = 1;
|
||||
}
|
||||
|
||||
message GetPasswordComplexitySettingsResponse {
|
||||
zitadel.object.v2alpha.Details details = 1;
|
||||
zitadel.settings.v2alpha.PasswordComplexitySettings settings = 2;
|
||||
}
|
||||
|
||||
message GetBrandingSettingsRequest {
|
||||
zitadel.object.v2alpha.RequestContext ctx = 1;
|
||||
}
|
||||
|
||||
message GetBrandingSettingsResponse {
|
||||
zitadel.object.v2alpha.Details details = 1;
|
||||
zitadel.settings.v2alpha.BrandingSettings settings = 2;
|
||||
}
|
||||
|
||||
message GetDomainSettingsRequest {
|
||||
zitadel.object.v2alpha.RequestContext ctx = 1;
|
||||
}
|
||||
|
||||
message GetDomainSettingsResponse {
|
||||
zitadel.object.v2alpha.Details details = 1;
|
||||
zitadel.settings.v2alpha.DomainSettings settings = 2;
|
||||
}
|
||||
|
||||
message GetLegalAndSupportSettingsRequest {
|
||||
zitadel.object.v2alpha.RequestContext ctx = 1;
|
||||
}
|
||||
|
||||
message GetLegalAndSupportSettingsResponse {
|
||||
zitadel.object.v2alpha.Details details = 1;
|
||||
zitadel.settings.v2alpha.LegalAndSupportSettings settings = 2;
|
||||
}
|
||||
|
||||
message GetLockoutSettingsRequest {
|
||||
zitadel.object.v2alpha.RequestContext ctx = 1;
|
||||
}
|
||||
|
||||
message GetLockoutSettingsResponse {
|
||||
zitadel.object.v2alpha.Details details = 1;
|
||||
zitadel.settings.v2alpha.LockoutSettings settings = 2;
|
||||
}
|
||||
|
||||
message GetActiveIdentityProvidersRequest {
|
||||
zitadel.object.v2alpha.RequestContext ctx = 1;
|
||||
}
|
||||
|
||||
message GetActiveIdentityProvidersResponse {
|
||||
zitadel.object.v2alpha.ListDetails details = 1;
|
||||
repeated zitadel.settings.v2alpha.IdentityProvider identity_providers = 2;
|
||||
}
|
||||
|
||||
message GetGeneralSettingsRequest {}
|
||||
|
||||
message GetGeneralSettingsResponse {
|
||||
string default_org_id = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "default organization for the current context"
|
||||
}
|
||||
];
|
||||
string default_language = 2 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "default language for the current context"
|
||||
example: "\"en\""
|
||||
}
|
||||
];
|
||||
repeated string supported_languages = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "[\"en\", \"de\", \"it\"]"
|
||||
}
|
||||
];
|
||||
}
|
@ -229,7 +229,7 @@ service SystemService {
|
||||
};
|
||||
}
|
||||
|
||||
// Returns the domain of an instance
|
||||
// Removes the domain of an instance
|
||||
rpc RemoveDomain(RemoveDomainRequest) returns (RemoveDomainResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/instances/{instance_id}/domains/{domain}";
|
||||
@ -240,7 +240,7 @@ service SystemService {
|
||||
};
|
||||
}
|
||||
|
||||
// Returns the domain of an instance
|
||||
// Sets the primary domain of an instance
|
||||
rpc SetPrimaryDomain(SetPrimaryDomainRequest) returns (SetPrimaryDomainResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/instances/{instance_id}/domains/_set_primary";
|
||||
|
Loading…
Reference in New Issue
Block a user