mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-13 03:24:26 +00:00
Merge branch 'main' into next
This commit is contained in:
commit
937f683ce5
20
.github/workflows/release.yml
vendored
20
.github/workflows/release.yml
vendored
@ -126,3 +126,23 @@ jobs:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
run: |
|
||||
gh workflow -R zitadel/homebrew-tap run update.yml -f runId=${RUN_ID} -f version=${VERSION}
|
||||
|
||||
helm-chart:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: version
|
||||
if: ${{ github.ref_name == 'next' }}
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: generate token
|
||||
uses: tibdex/github-app-token@v2
|
||||
id: generate-token
|
||||
with:
|
||||
app_id: ${{ secrets.APP_ID }}
|
||||
private_key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
- name: Trigger Chart Bump
|
||||
env:
|
||||
VERSION: ${{ needs.version.outputs.version }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
run: |
|
||||
gh workflow -R zitadel/zitadel-charts run bump.yml
|
||||
|
27
cmd/setup/32.go
Normal file
27
cmd/setup/32.go
Normal file
@ -0,0 +1,27 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed 32.sql
|
||||
addAuthSessionID string
|
||||
)
|
||||
|
||||
type AddAuthSessionID struct {
|
||||
dbClient *database.DB
|
||||
}
|
||||
|
||||
func (mig *AddAuthSessionID) Execute(ctx context.Context, _ eventstore.Event) error {
|
||||
_, err := mig.dbClient.ExecContext(ctx, addAuthSessionID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (mig *AddAuthSessionID) String() string {
|
||||
return "32_add_auth_sessionID"
|
||||
}
|
3
cmd/setup/32.sql
Normal file
3
cmd/setup/32.sql
Normal file
@ -0,0 +1,3 @@
|
||||
ALTER TABLE IF EXISTS auth.user_sessions ADD COLUMN IF NOT EXISTS id TEXT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS user_session_id ON auth.user_sessions (id, instance_id);
|
@ -116,6 +116,7 @@ type Steps struct {
|
||||
s29FillFieldsForProjectGrant *FillFieldsForProjectGrant
|
||||
s30FillFieldsForOrgDomainVerified *FillFieldsForOrgDomainVerified
|
||||
s31AddAggregateIndexToFields *AddAggregateIndexToFields
|
||||
s32AddAuthSessionID *AddAuthSessionID
|
||||
}
|
||||
|
||||
func MustNewSteps(v *viper.Viper) *Steps {
|
||||
|
@ -160,6 +160,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
|
||||
steps.s29FillFieldsForProjectGrant = &FillFieldsForProjectGrant{eventstore: eventstoreClient}
|
||||
steps.s30FillFieldsForOrgDomainVerified = &FillFieldsForOrgDomainVerified{eventstore: eventstoreClient}
|
||||
steps.s31AddAggregateIndexToFields = &AddAggregateIndexToFields{dbClient: esPusherDBClient}
|
||||
steps.s32AddAuthSessionID = &AddAuthSessionID{dbClient: esPusherDBClient}
|
||||
|
||||
err = projection.Create(ctx, projectionDBClient, eventstoreClient, config.Projections, nil, nil, nil)
|
||||
logging.OnError(err).Fatal("unable to start projections")
|
||||
@ -216,6 +217,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
|
||||
steps.s21AddBlockFieldToLimits,
|
||||
steps.s25User11AddLowerFieldsToVerifiedEmail,
|
||||
steps.s27IDPTemplate6SAMLNameIDFormat,
|
||||
steps.s32AddAuthSessionID,
|
||||
} {
|
||||
mustExecuteMigration(ctx, eventstoreClient, step, "migration failed")
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import localeCs from '@angular/common/locales/cs';
|
||||
import localeEn from '@angular/common/locales/en';
|
||||
import localeEs from '@angular/common/locales/es';
|
||||
import localeFr from '@angular/common/locales/fr';
|
||||
import localeId from '@angular/common/locales/id';
|
||||
import localeIt from '@angular/common/locales/it';
|
||||
import localeJa from '@angular/common/locales/ja';
|
||||
import localeMk from '@angular/common/locales/mk';
|
||||
@ -80,6 +81,8 @@ registerLocaleData(localeEs);
|
||||
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/es.json'));
|
||||
registerLocaleData(localeFr);
|
||||
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/fr.json'));
|
||||
registerLocaleData(localeId);
|
||||
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/id.json'));
|
||||
registerLocaleData(localeIt);
|
||||
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/it.json'));
|
||||
registerLocaleData(localeJa);
|
||||
|
@ -348,6 +348,61 @@
|
||||
</div>
|
||||
<cnsl-info-section class="feature-info">{{ 'SETTING.FEATURES.ACTIONS_DESCRIPTION' | translate }}</cnsl-info-section>
|
||||
</div>
|
||||
|
||||
<div class="feature-row" *ngIf="toggleStates.oidcSingleV1SessionTermination">
|
||||
<span>{{ 'SETTING.FEATURES.OIDCSINGLEV1SESSIONTERMINATION' | translate }}</span>
|
||||
<div class="row">
|
||||
<mat-button-toggle-group
|
||||
class="theme-toggle"
|
||||
class="buttongroup"
|
||||
[(ngModel)]="toggleStates.oidcSingleV1SessionTermination.state"
|
||||
(change)="validateAndSave()"
|
||||
name="displayview"
|
||||
aria-label="Display View"
|
||||
>
|
||||
<mat-button-toggle [value]="ToggleState.INHERITED">
|
||||
<div class="toggle-row">
|
||||
<span>{{ 'SETTING.FEATURES.STATES.INHERITED' | translate }}</span>
|
||||
<i
|
||||
class="info-i las la-question-circle"
|
||||
matTooltip="{{ 'SETTING.FEATURES.INHERITED_DESCRIPTION' | translate }}"
|
||||
></i>
|
||||
<div
|
||||
*ngIf="
|
||||
!!featureData.oidcSingleV1SessionTermination?.enabled &&
|
||||
(featureData.oidcSingleV1SessionTermination?.source === Source.SOURCE_SYSTEM ||
|
||||
featureData.oidcSingleV1SessionTermination?.source === Source.SOURCE_UNSPECIFIED)
|
||||
"
|
||||
class="current-dot enabled"
|
||||
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.ENABLED' | translate }}"
|
||||
></div>
|
||||
<div
|
||||
*ngIf="
|
||||
!featureData.oidcSingleV1SessionTermination?.enabled &&
|
||||
(featureData.oidcSingleV1SessionTermination?.source === Source.SOURCE_SYSTEM ||
|
||||
featureData.oidcSingleV1SessionTermination?.source === Source.SOURCE_UNSPECIFIED)
|
||||
"
|
||||
class="current-dot disabled"
|
||||
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.DISABLED' | translate }}"
|
||||
></div>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle [value]="ToggleState.DISABLED">
|
||||
<div class="toggle-row">
|
||||
<span> {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }}</span>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle [value]="ToggleState.ENABLED">
|
||||
<div class="toggle-row">
|
||||
<span> {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }}</span>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
</div>
|
||||
<cnsl-info-section class="feature-info">{{
|
||||
'SETTING.FEATURES.OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION' | translate
|
||||
}}</cnsl-info-section>
|
||||
</div>
|
||||
</div>
|
||||
</cnsl-card>
|
||||
</div>
|
||||
|
@ -38,6 +38,7 @@ type ToggleStates = {
|
||||
userSchema?: FeatureState;
|
||||
oidcTokenExchange?: FeatureState;
|
||||
actions?: FeatureState;
|
||||
oidcSingleV1SessionTermination?: FeatureState;
|
||||
};
|
||||
|
||||
@Component({
|
||||
@ -135,6 +136,12 @@ export class FeaturesComponent implements OnDestroy {
|
||||
req.setActions(this.toggleStates?.actions?.state === ToggleState.ENABLED);
|
||||
changed = true;
|
||||
}
|
||||
if (this.toggleStates?.oidcSingleV1SessionTermination?.state !== ToggleState.INHERITED) {
|
||||
req.setOidcSingleV1SessionTermination(
|
||||
this.toggleStates?.oidcSingleV1SessionTermination?.state === ToggleState.ENABLED,
|
||||
);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
this.featureService
|
||||
@ -215,6 +222,16 @@ export class FeaturesComponent implements OnDestroy {
|
||||
? ToggleState.ENABLED
|
||||
: ToggleState.DISABLED,
|
||||
},
|
||||
oidcSingleV1SessionTermination: {
|
||||
source: this.featureData.oidcSingleV1SessionTermination?.source || Source.SOURCE_SYSTEM,
|
||||
state:
|
||||
this.featureData.oidcSingleV1SessionTermination?.source === Source.SOURCE_SYSTEM ||
|
||||
this.featureData.oidcSingleV1SessionTermination?.source === Source.SOURCE_UNSPECIFIED
|
||||
? ToggleState.INHERITED
|
||||
: !!this.featureData.oidcSingleV1SessionTermination?.enabled
|
||||
? ToggleState.ENABLED
|
||||
: ToggleState.DISABLED,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -232,24 +249,4 @@ export class FeaturesComponent implements OnDestroy {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
public saveFeatures(): void {
|
||||
if (this.featureData) {
|
||||
const req = new SetInstanceFeaturesRequest();
|
||||
req.setLoginDefaultOrg(!!this.featureData.loginDefaultOrg?.enabled);
|
||||
req.setOidcLegacyIntrospection(!!this.featureData.oidcLegacyIntrospection?.enabled);
|
||||
req.setOidcTokenExchange(!!this.featureData.oidcTokenExchange?.enabled);
|
||||
req.setOidcTriggerIntrospectionProjections(!!this.featureData.oidcTriggerIntrospectionProjections?.enabled);
|
||||
req.setUserSchema(!!this.featureData.userSchema?.enabled);
|
||||
|
||||
this.featureService
|
||||
.setInstanceFeatures(req)
|
||||
.then(() => {
|
||||
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -179,7 +179,7 @@ export class ProviderLDAPComponent {
|
||||
}
|
||||
|
||||
public submitForm(): void {
|
||||
this.provider ? this.updateLDAPProvider() : this.addLDAPProvider();
|
||||
this.provider || this.justCreated$.value ? this.updateLDAPProvider() : this.addLDAPProvider();
|
||||
}
|
||||
|
||||
public addLDAPProvider(): void {
|
||||
|
@ -1,3 +1,20 @@
|
||||
export const supportedLanguages = ['de', 'en', 'es', 'fr', 'it', 'ja', 'pl', 'zh', 'bg', 'pt', 'mk', 'cs', 'ru', 'nl', 'sv'];
|
||||
export const supportedLanguagesRegexp: RegExp = /de|en|es|fr|it|ja|pl|zh|bg|pt|mk|cs|ru|nl|sv/;
|
||||
export const supportedLanguages = [
|
||||
'de',
|
||||
'en',
|
||||
'es',
|
||||
'fr',
|
||||
'id',
|
||||
'it',
|
||||
'ja',
|
||||
'pl',
|
||||
'zh',
|
||||
'bg',
|
||||
'pt',
|
||||
'mk',
|
||||
'cs',
|
||||
'ru',
|
||||
'nl',
|
||||
'sv',
|
||||
];
|
||||
export const supportedLanguagesRegexp: RegExp = /de|en|es|fr|id|it|ja|pl|zh|bg|pt|mk|cs|ru|nl|sv/;
|
||||
export const fallbackLanguage: string = 'en';
|
||||
|
@ -1367,7 +1367,7 @@
|
||||
"ALLOWED_SAVED": "Разрешените езици са запазени успешно.",
|
||||
"OPTIONS": {
|
||||
"de": "Deutsch",
|
||||
"en": "Английски",
|
||||
"en": "English",
|
||||
"es": "Español",
|
||||
"fr": "Français",
|
||||
"it": "Италиано",
|
||||
@ -1380,7 +1380,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
}
|
||||
},
|
||||
"SMTP": {
|
||||
@ -1467,6 +1468,8 @@
|
||||
"USERSCHEMA_DESCRIPTION": "Потребителските схеми позволяват управление на данните за схемите на потребителите. Ако е активиран флагът, ще можете да използвате новото API и неговите функции.",
|
||||
"ACTIONS": "Действия",
|
||||
"ACTIONS_DESCRIPTION": "Действия v2 позволяват управление на выполнения на данни и цели. Ако флагът е активиран, ще можете да използвате новия API и неговите функции.",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 Завършване на сесия",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "Ако флагът е активиран, ще можете да прекратите единична сесия от UI за вход, като предоставите id_token с `sid` претенция като id_token_hint на крайната точка на end_session. Имайте предвид, че в момента всички сесии от същия потребителски агент (браузър) се прекратяват в UI за вход. Сесиите, управлявани чрез API на сесията, вече позволяват прекратяването на единични сесии.",
|
||||
"STATES": {
|
||||
"INHERITED": "Наследено",
|
||||
"ENABLED": "Активирано",
|
||||
@ -1599,7 +1602,7 @@
|
||||
"LANGUAGE": "Език",
|
||||
"LANGUAGES": {
|
||||
"de": "Deutsch",
|
||||
"en": "Английски",
|
||||
"en": "English",
|
||||
"es": "Español",
|
||||
"fr": "Français",
|
||||
"it": "Италиано",
|
||||
@ -1612,7 +1615,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"KEYS": {
|
||||
"emailVerificationDoneText": "Проверката на имейл е извършена",
|
||||
@ -2535,7 +2539,7 @@
|
||||
},
|
||||
"LANGUAGES": {
|
||||
"de": "Deutsch",
|
||||
"en": "Английски",
|
||||
"en": "English",
|
||||
"es": "Español",
|
||||
"fr": "Français",
|
||||
"it": "Италиано",
|
||||
@ -2548,7 +2552,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"MEMBER": {
|
||||
"ADD": "Добавяне на мениджър",
|
||||
|
@ -1381,7 +1381,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
}
|
||||
},
|
||||
"SMTP": {
|
||||
@ -1468,6 +1469,8 @@
|
||||
"USERSCHEMA_DESCRIPTION": "Schémata uživatelů umožňují spravovat datová schémata uživatelů. Pokud je příznak povolen, budete moci používat nové API a jeho funkce.",
|
||||
"ACTIONS": "Akce",
|
||||
"ACTIONS_DESCRIPTION": "Akce v2 umožňují správu datových provedení a cílů. Pokud je tento příznak povolen, budete moci používat nové API a jeho funkce.",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 ukončení relace",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "Pokud je příznak aktivován, budete moci ukončit jedinou relaci z rozhraní pro přihlášení zadáním id_token s nárokem `sid` jako id_token_hint na koncovém bodu end_session. Poznamenejte si, že v současné době jsou v rozhraní pro přihlášení ukončeny všechny relace ze stejného uživatelského agenta (prohlížeče). Relace spravované prostřednictvím rozhraní API relace již umožňují ukončení jednotlivých relací.",
|
||||
"STATES": {
|
||||
"INHERITED": "Děděno",
|
||||
"ENABLED": "Povoleno",
|
||||
@ -1613,7 +1616,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"KEYS": {
|
||||
"emailVerificationDoneText": "Ověření e-mailu dokončeno",
|
||||
@ -2561,7 +2565,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"MEMBER": {
|
||||
"ADD": "Přidat manažera",
|
||||
|
@ -1381,7 +1381,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
}
|
||||
},
|
||||
"SMTP": {
|
||||
@ -1468,6 +1469,8 @@
|
||||
"USERSCHEMA_DESCRIPTION": "Benutzerschemata ermöglichen das Verwalten von Datenschemata von Benutzern. Wenn die Flagge aktiviert ist, können Sie die neue API und ihre Funktionen verwenden.",
|
||||
"ACTIONS": "Aktionen",
|
||||
"ACTIONS_DESCRIPTION": "Aktionen v2 ermöglichen die Verwaltung von Datenausführungen und Zielen. Wenn das Flag aktiviert ist, können Sie die neue API und ihre Funktionen verwenden.",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 Sitzungsbeendigung",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "Wenn das Flag aktiviert ist, können Sie eine einzelne Sitzung über die Login-Benutzeroberfläche beenden, indem Sie einen id_token mit einem `sid` Claim als id_token_hint am Endpunkt end_session übergeben. Beachten Sie, dass derzeit alle Sitzungen desselben Benutzeragenten (Browser) in der Login-Benutzeroberfläche beendet werden. Sitzungen, die über die Session API verwaltet werden, ermöglichen bereits die Beendigung einzelner Sitzungen.",
|
||||
"STATES": {
|
||||
"INHERITED": "Erben",
|
||||
"ENABLED": "Aktiviert",
|
||||
@ -1613,7 +1616,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"KEYS": {
|
||||
"emailVerificationDoneText": "Email Verification erfolgreich",
|
||||
@ -2552,7 +2556,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"MEMBER": {
|
||||
"ADD": "Manager hinzufügen",
|
||||
|
@ -1381,7 +1381,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
}
|
||||
},
|
||||
"SMTP": {
|
||||
@ -1468,6 +1469,8 @@
|
||||
"USERSCHEMA_DESCRIPTION": "User Schemas allow to manage data schemas of user. If the flag is enabled, you'll be able to use the new API and its features.",
|
||||
"ACTIONS": "Actions",
|
||||
"ACTIONS_DESCRIPTION": "Actions v2 allow to manage data executions and targets. If the flag is enabled, you'll be able to use the new API and its features.",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 Session Termination",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "If the flag is enabled, you'll be able to terminate a single session from the login UI by providing an id_token with a `sid` claim as id_token_hint on the end_session endpoint. Note that currently all sessions from the same user agent (browser) are terminated in the login UI. Sessions managed through the Session API already allow the termination of single sessions.",
|
||||
"STATES": {
|
||||
"INHERITED": "Inherit",
|
||||
"ENABLED": "Enabled",
|
||||
@ -1613,7 +1616,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"KEYS": {
|
||||
"emailVerificationDoneText": "Email verification done",
|
||||
@ -2577,7 +2581,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"MEMBER": {
|
||||
"ADD": "Add a Manager",
|
||||
|
@ -1382,7 +1382,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
}
|
||||
},
|
||||
"SMTP": {
|
||||
@ -1469,6 +1470,8 @@
|
||||
"USERSCHEMA_DESCRIPTION": "Los esquemas de usuario permiten gestionar los esquemas de datos de los usuarios. Si se activa la bandera, podrás utilizar la nueva API y sus funciones.",
|
||||
"ACTIONS": "Acciones",
|
||||
"ACTIONS_DESCRIPTION": "Acciones v2 permite administrar las ejecuciones y objetivos de datos. Si la bandera está habilitada, podrá utilizar la nueva API y sus funciones.",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 Terminación de sesión",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "Si la bandera está habilitada, podrá terminar una sesión única desde la interfaz de usuario de inicio de sesión proporcionando un id_token con una reclamación `sid` como id_token_hint en el punto final de end_session. Tenga en cuenta que actualmente se terminan todas las sesiones del mismo agente de usuario (navegador) en la interfaz de usuario de inicio de sesión. Las sesiones administradas a través de la API de sesión ya permiten la terminación de sesiones individuales.",
|
||||
"STATES": {
|
||||
"INHERITED": "Heredado",
|
||||
"ENABLED": "Habilitado",
|
||||
@ -1614,7 +1617,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"KEYS": {
|
||||
"emailVerificationDoneText": "Verificación de email realizada",
|
||||
@ -2549,7 +2553,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"MEMBER": {
|
||||
"ADD": "Añadir un Mánager",
|
||||
|
@ -1381,7 +1381,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
}
|
||||
},
|
||||
"SMTP": {
|
||||
@ -1468,6 +1469,8 @@
|
||||
"USERSCHEMA_DESCRIPTION": "Les schémas utilisateur permettent de gérer les schémas de données des utilisateurs. Si le drapeau est activé, vous pourrez utiliser la nouvelle API et ses fonctionnalités.",
|
||||
"ACTIONS": "Actions",
|
||||
"ACTIONS_DESCRIPTION": "Les actions v2 permettent de gérer les exécutions et les cibles de données. Si l'indicateur est activé, vous pourrez utiliser la nouvelle API et ses fonctionnalités.",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 Fin de session",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "Si l'indicateur est activé, vous pourrez terminer une seule session à partir de l'interface utilisateur de connexion en fournissant un id_token avec une revendication `sid` en tant que id_token_hint sur le point de terminaison end_session. Notez que toutes les sessions du même agent utilisateur (navigateur) sont actuellement terminées dans l'interface utilisateur de connexion. Les sessions gérées via l'API de session permettent déjà la terminaison de sessions individuelles.",
|
||||
"STATES": {
|
||||
"INHERITED": "Hérité",
|
||||
"ENABLED": "Activé",
|
||||
@ -1613,7 +1616,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"KEYS": {
|
||||
"emailVerificationDoneText": "Vérification de l'e-mail effectuée",
|
||||
@ -2553,7 +2557,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"MEMBER": {
|
||||
"ADD": "Ajouter un responsable",
|
||||
|
2329
console/src/assets/i18n/id.json
Normal file
2329
console/src/assets/i18n/id.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1381,7 +1381,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
}
|
||||
},
|
||||
"SMTP": {
|
||||
@ -1468,6 +1469,8 @@
|
||||
"USERSCHEMA_DESCRIPTION": "Gli schemi utente consentono di gestire gli schemi di dati degli utenti. Se la flag è attivata, sarà possibile utilizzare la nuova API e le sue funzionalità.",
|
||||
"ACTIONS": "Azioni",
|
||||
"ACTIONS_DESCRIPTION": "Le azioni v2 consentono di gestire le esecuzioni e gli obiettivi dei dati. Se l'indicatore è abilitato, potrai utilizzare la nuova API e le sue funzionalità.",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 Terminazione sessione",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "Se il flag è abilitato, sarai in grado di terminare una singola sessione dall'interfaccia utente di accesso fornendo un id_token con una richiesta `sid` come id_token_hint nel punto finale di end_session. Tieni presente che attualmente tutte le sessioni dello stesso agente utente (browser) vengono terminate nell'interfaccia utente di accesso. Le sessioni gestite tramite l'API di sessione consentono già la terminazione di singole sessioni.",
|
||||
"STATES": {
|
||||
"INHERITED": "Predefinito",
|
||||
"ENABLED": "Abilitato",
|
||||
@ -1613,7 +1616,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"KEYS": {
|
||||
"emailVerificationDoneText": "Verifica dell'e-mail terminata con successo.",
|
||||
@ -2553,7 +2557,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"MEMBER": {
|
||||
"ADD": "Aggiungi un manager",
|
||||
|
@ -1381,7 +1381,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
}
|
||||
},
|
||||
"SMTP": {
|
||||
@ -1468,6 +1469,8 @@
|
||||
"USERSCHEMA_DESCRIPTION": "ユーザー スキーマを使用すると、ユーザーのデータスキーマを管理できます。フラグが有効になっている場合、新しい APIとその機能を使用できます。",
|
||||
"ACTIONS": "アクション",
|
||||
"ACTIONS_DESCRIPTION": "Actions v2は、データの実行とターゲットを管理できます。フラグが有効になっている場合、新しい APIとその機能を使用できます。",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 セッション終了",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "フラグが有効になっている場合、id_token を `sid` クレームと共に id_token_hint として end_session エンドポイントに提供することで、ログイン UI から単一のセッションを終了できるようになります。 現在、同じユーザー エージェント (ブラウザ) からのすべてのセッションがログイン UI で終了することに注意してください。 セッション API を通じて管理されるセッションは、すでに単一のセッションの終了を許可しています。",
|
||||
"STATES": {
|
||||
"INHERITED": "継承",
|
||||
"ENABLED": "有効",
|
||||
@ -1609,7 +1612,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"KEYS": {
|
||||
"emailVerificationDoneText": "メール認証が完了しました",
|
||||
@ -2544,7 +2548,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"MEMBER": {
|
||||
"ADD": "マネージャーを追加する",
|
||||
|
@ -1382,7 +1382,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
}
|
||||
},
|
||||
"SMTP": {
|
||||
@ -1469,6 +1470,8 @@
|
||||
"USERSCHEMA_DESCRIPTION": "Корисничките шеми овозможуваат управување со податоци шеми на корисникот. Ако знамето е овозможено, ќе можете да го користите новиот API и неговите функции.",
|
||||
"ACTIONS": "Акции",
|
||||
"ACTIONS_DESCRIPTION": "Акциите v2 овозможуваат управување со извршување на податоци и цели. Ако знамето е овозможено, ќе можете да го користите новиот API и неговите функции.",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 Завршување на сесија",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "Ако ознаката е активирана, ќе можете да ја завршите единечна сесија од корисничкиот интерфејс за најава, со обезбедување id_token со `sid` побарување како id_token_hint на крајната точка на end_session. Имајте предвид дека во моментов сите сесии од истиот кориснички агент (прелистувач) се завршуваат во корисничкиот интерфејс за најава. Сесиите управувани преку API на сесија веќе дозволуваат завршување на единечни сесии.",
|
||||
"STATES": {
|
||||
"INHERITED": "Наследи",
|
||||
"ENABLED": "Овозможено",
|
||||
@ -1614,7 +1617,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"KEYS": {
|
||||
"emailVerificationDoneText": "Е-поштата е верифицирана",
|
||||
@ -2549,7 +2553,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"MEMBER": {
|
||||
"ADD": "Додај Менаџер",
|
||||
|
@ -1468,6 +1468,8 @@
|
||||
"USERSCHEMA_DESCRIPTION": "Met gebruikerschema's kunt u de dataschema's van gebruikers beheren. Als de vlag is ingeschakeld, kunt u de nieuwe API en zijn functies gebruiken.",
|
||||
"ACTIONS": "Acties",
|
||||
"ACTIONS_DESCRIPTION": "Actions v2 maken het mogelijk om data-uitvoeringen en doelen te beheren. Als de vlag is ingeschakeld, kunt u de nieuwe API en zijn functies gebruiken.",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 Sessiebeëindiging",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "Als het vlagje is ingeschakeld, kunt u een enkele sessie beëindigen via de login-gebruikersinterface door een id_token met een `sid`-claim als id_token_hint op het eindpunt end_session te verstrekken. Houd er rekening mee dat momenteel alle sessies van dezelfde gebruikersagent (browser) worden beëindigd in de login-gebruikersinterface. Sessies die worden beheerd via de Session API staan al toe om individuele sessies te beëindigen.",
|
||||
"STATES": {
|
||||
"INHERITED": "Overgenomen",
|
||||
"ENABLED": "Ingeschakeld",
|
||||
@ -1613,7 +1615,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"KEYS": {
|
||||
"emailVerificationDoneText": "E-mail verificatie voltooid",
|
||||
@ -2570,7 +2573,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"MEMBER": {
|
||||
"ADD": "Voeg een Manager toe",
|
||||
|
@ -1380,7 +1380,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
}
|
||||
},
|
||||
"SMTP": {
|
||||
@ -1467,6 +1468,8 @@
|
||||
"USERSCHEMA_DESCRIPTION": "Schematy użytkowników umożliwiają zarządzanie schematami danych użytkowników. Jeśli flaga jest włączona, będziesz mógł korzystać z nowego interfejsu API i jego funkcji.",
|
||||
"ACTIONS": "Akcje",
|
||||
"ACTIONS_DESCRIPTION": "Akcje v2 umożliwiają zarządzanie wykonaniami danych i celami. Jeżeli flaga jest włączona, będziesz mógł korzystać z nowego interfejsu API i jego funkcji.",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 Zakończenie sesji",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "Jeśli flaga jest włączona, będziesz mógł zakończyć pojedynczą sesję z interfejsu użytkownika logowania, podając id_token z roszczeniem `sid` jako id_token_hint w punkcie końcowym end_session. Należy pamiętać, że obecnie wszystkie sesje z tego samego agenta użytkownika (przeglądarki) są kończone w interfejsie użytkownika logowania. Sesje zarządzane za pomocą interfejsu API sesji już pozwalają na zakończenie pojedynczych sesji.",
|
||||
"STATES": {
|
||||
"INHERITED": "Dziedziczony",
|
||||
"ENABLED": "Włączony",
|
||||
@ -1612,7 +1615,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"KEYS": {
|
||||
"emailVerificationDoneText": "Weryfikacja adresu e-mail zakończona",
|
||||
@ -2552,7 +2556,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"MEMBER": {
|
||||
"ADD": "Dodaj managera",
|
||||
|
@ -1382,7 +1382,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
}
|
||||
},
|
||||
"SMTP": {
|
||||
@ -1469,6 +1470,8 @@
|
||||
"USERSCHEMAS_DESCRIPTION": "Esquemas de Usuário permitem gerenciar esquemas de dados do usuário. Se o sinalizador estiver ativado, você poderá usar a nova API e seus recursos.",
|
||||
"ACTIONS": "Ações",
|
||||
"ACTIONS_DESCRIPTION": "Actions v2 permitem gerenciar execuções e destinos de dados. Se a flag estiver habilitada, você poderá usar a nova API e seus recursos.",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 Término de sessão",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "Se a bandeira estiver habilitada, você poderá encerrar uma sessão única da interface do usuário de login fornecendo um id_token com uma reivindicação `sid como id_token_hint no ponto final de end_session. Observe que atualmente todas as sessões do mesmo agente de usuário (navegador) são encerradas na interface do usuário de login. As sessões gerenciadas por meio da API de sessão já permitem o encerramento de sessões individuais.",
|
||||
"STATES": {
|
||||
"INHERITED": "Herdade",
|
||||
"ENABLED": "Habilitado",
|
||||
@ -1614,7 +1617,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"KEYS": {
|
||||
"emailVerificationDoneText": "Verificação de email concluída",
|
||||
@ -2547,7 +2551,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"MEMBER": {
|
||||
"ADD": "Adicionar um Gerente",
|
||||
|
@ -1422,9 +1422,11 @@
|
||||
"bg": "Български",
|
||||
"pt": "Portuguese",
|
||||
"mk": "Македонски",
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
}
|
||||
},
|
||||
"SMTP": {
|
||||
@ -1519,6 +1521,8 @@
|
||||
"USERSCHEMA_DESCRIPTION": "Схемы пользователей позволяют управлять схемами данных пользователей. Если флаг включен, вы сможете использовать новый API и его функции.",
|
||||
"ACTIONS": "Действия",
|
||||
"ACTIONS_DESCRIPTION": "Actions v2 позволяют управлять выполнением данных и целевыми объектами. Если флаг включен, вы сможете использовать новый API и его функции.",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 Окончание сеанса",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "Если флаг включен, вы сможете завершить отдельный сеанс из интерфейса пользователя входа, предоставив id_token с претензией `sid` в качестве id_token_hint на конечной точке end_session. Обратите внимание, что в настоящее время все сеансы одного и того же пользовательского агента (браузера) завершаются в интерфейсе пользователя входа. Сеансы, управляемые через API сеанса, уже позволяют завершать отдельные сеансы.",
|
||||
"STATES": {
|
||||
"INHERITED": "Наследовать",
|
||||
"ENABLED": "Включено",
|
||||
@ -1668,7 +1672,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"LOCALE": "Код языка",
|
||||
"LOCALES": {
|
||||
@ -2655,9 +2660,11 @@
|
||||
"bg": "Български",
|
||||
"pt": "Portuguese",
|
||||
"mk": "Македонски",
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"MEMBER": {
|
||||
"ADD": "Добавить менеджера",
|
||||
|
@ -1385,7 +1385,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
}
|
||||
},
|
||||
"SMTP": {
|
||||
@ -1472,6 +1473,8 @@
|
||||
"USERSCHEMA_DESCRIPTION": "Användarscheman tillåter att hantera datascheman för användare. Om flaggan är aktiverad kommer du att kunna använda det nya API:et och dess funktioner.",
|
||||
"ACTIONS": "Åtgärder",
|
||||
"ACTIONS_DESCRIPTION": "Åtgärder v2 tillåter att hantera dataexekveringar och mål. Om flaggan är aktiverad kommer du att kunna använda det nya API:et och dess funktioner.",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 Session avslutning",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "Om flaggan är aktiverad, kan du avsluta en enskild session från inloggningsgränssnittet genom att ange en id_token med ett `sid`-krav som id_token_hint på slutpunkten end_session. Observera att för närvarande alla sessioner från samma användaragent (webbläsare) avslutas i inloggningsgränssnittet. Sessioner som hanteras via Session API tillåter redan avslutning av enskilda sessioner.",
|
||||
"STATES": {
|
||||
"INHERITED": "Ärv",
|
||||
"ENABLED": "Aktiverad",
|
||||
@ -1617,7 +1620,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"KEYS": {
|
||||
"emailVerificationDoneText": "E-postverifiering klar",
|
||||
@ -2576,12 +2580,13 @@
|
||||
"pl": "Polski",
|
||||
"zh": "简体中文",
|
||||
"bg": "Български",
|
||||
"pt": "Português",
|
||||
"pt": "Portuguese",
|
||||
"mk": "Македонски",
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"MEMBER": {
|
||||
"ADD": "Lägg till en administratör",
|
||||
|
@ -1381,7 +1381,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
}
|
||||
},
|
||||
"SMTP": {
|
||||
@ -1468,6 +1469,8 @@
|
||||
"USERSCHEMA_DESCRIPTION": "用户架构允许管理用户的数据架构。如果启用此标志,您将可以使用新的 API 及其功能。",
|
||||
"ACTIONS": "操作",
|
||||
"ACTIONS_DESCRIPTION": "Actions v2 可以管理数据执行和目标。如果启用此标志,您将可以使用新的 API 及其功能。",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 终止会话",
|
||||
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "如果启用了标志,您可以通过在 end_session 端点上提供带有 `sid` 声明的 id_token 作为 id_token_hint 来从登录 UI 终止单个会话。 请注意,目前所有来自同一用户代理(浏览器)的会话都在登录 UI 中终止。 通过会话 API 管理的会话已经允许终止单个会话。",
|
||||
"STATES": {
|
||||
"INHERITED": "继承",
|
||||
"ENABLED": "已启用",
|
||||
@ -1612,7 +1615,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"KEYS": {
|
||||
"emailVerificationDoneText": "电子邮件验证完成",
|
||||
@ -2552,7 +2556,8 @@
|
||||
"cs": "Čeština",
|
||||
"ru": "Русский",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Svenska"
|
||||
"sv": "Svenska",
|
||||
"id": "Bahasa Indonesia"
|
||||
},
|
||||
"MEMBER": {
|
||||
"ADD": "添加管理者",
|
||||
|
@ -38,6 +38,7 @@ ZITADEL is available in the following languages
|
||||
- English (en)
|
||||
- Spanish (es)
|
||||
- French (fr)
|
||||
- Indonesian (id)
|
||||
- Italian (it)
|
||||
- 日本語 (ja)
|
||||
- Polish(pl)
|
||||
|
@ -17,6 +17,7 @@ func systemFeaturesToCommand(req *feature_pb.SetSystemFeaturesRequest) *command.
|
||||
Actions: req.Actions,
|
||||
TokenExchange: req.OidcTokenExchange,
|
||||
ImprovedPerformance: improvedPerformanceListToDomain(req.ImprovedPerformance),
|
||||
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +31,7 @@ func systemFeaturesToPb(f *query.SystemFeatures) *feature_pb.GetSystemFeaturesRe
|
||||
OidcTokenExchange: featureSourceToFlagPb(&f.TokenExchange),
|
||||
Actions: featureSourceToFlagPb(&f.Actions),
|
||||
ImprovedPerformance: featureSourceToImprovedPerformanceFlagPb(&f.ImprovedPerformance),
|
||||
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +46,7 @@ func instanceFeaturesToCommand(req *feature_pb.SetInstanceFeaturesRequest) *comm
|
||||
ImprovedPerformance: improvedPerformanceListToDomain(req.ImprovedPerformance),
|
||||
WebKey: req.WebKey,
|
||||
DebugOIDCParentError: req.DebugOidcParentError,
|
||||
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,6 +62,7 @@ func instanceFeaturesToPb(f *query.InstanceFeatures) *feature_pb.GetInstanceFeat
|
||||
ImprovedPerformance: featureSourceToImprovedPerformanceFlagPb(&f.ImprovedPerformance),
|
||||
WebKey: featureSourceToFlagPb(&f.WebKey),
|
||||
DebugOidcParentError: featureSourceToFlagPb(&f.DebugOIDCParentError),
|
||||
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ func Test_systemFeaturesToCommand(t *testing.T) {
|
||||
Actions: gu.Ptr(true),
|
||||
OidcTokenExchange: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
OidcSingleV1SessionTermination: gu.Ptr(true),
|
||||
}
|
||||
want := &command.SystemFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
@ -34,6 +35,7 @@ func Test_systemFeaturesToCommand(t *testing.T) {
|
||||
Actions: gu.Ptr(true),
|
||||
TokenExchange: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||
}
|
||||
got := systemFeaturesToCommand(arg)
|
||||
assert.Equal(t, want, got)
|
||||
@ -74,6 +76,10 @@ func Test_systemFeaturesToPb(t *testing.T) {
|
||||
Level: feature.LevelSystem,
|
||||
Value: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgByID},
|
||||
},
|
||||
OIDCSingleV1SessionTermination: query.FeatureSource[bool]{
|
||||
Level: feature.LevelSystem,
|
||||
Value: true,
|
||||
},
|
||||
}
|
||||
want := &feature_pb.GetSystemFeaturesResponse{
|
||||
Details: &object.Details{
|
||||
@ -109,6 +115,10 @@ func Test_systemFeaturesToPb(t *testing.T) {
|
||||
ExecutionPaths: []feature_pb.ImprovedPerformance{feature_pb.ImprovedPerformance_IMPROVED_PERFORMANCE_ORG_BY_ID},
|
||||
Source: feature_pb.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
OidcSingleV1SessionTermination: &feature_pb.FeatureFlag{
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
}
|
||||
got := systemFeaturesToPb(arg)
|
||||
assert.Equal(t, want, got)
|
||||
@ -124,6 +134,8 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
||||
Actions: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
WebKey: gu.Ptr(true),
|
||||
DebugOidcParentError: gu.Ptr(true),
|
||||
OidcSingleV1SessionTermination: gu.Ptr(true),
|
||||
}
|
||||
want := &command.InstanceFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
@ -134,6 +146,8 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
||||
Actions: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
WebKey: gu.Ptr(true),
|
||||
DebugOIDCParentError: gu.Ptr(true),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||
}
|
||||
got := instanceFeaturesToCommand(arg)
|
||||
assert.Equal(t, want, got)
|
||||
@ -178,6 +192,10 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
},
|
||||
OIDCSingleV1SessionTermination: query.FeatureSource[bool]{
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
},
|
||||
}
|
||||
want := &feature_pb.GetInstanceFeaturesResponse{
|
||||
Details: &object.Details{
|
||||
@ -221,6 +239,10 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
Enabled: false,
|
||||
Source: feature_pb.Source_SOURCE_UNSPECIFIED,
|
||||
},
|
||||
OidcSingleV1SessionTermination: &feature_pb.FeatureFlag{
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_INSTANCE,
|
||||
},
|
||||
}
|
||||
got := instanceFeaturesToPb(arg)
|
||||
assert.Equal(t, want, got)
|
||||
|
@ -17,6 +17,7 @@ func systemFeaturesToCommand(req *feature_pb.SetSystemFeaturesRequest) *command.
|
||||
Actions: req.Actions,
|
||||
TokenExchange: req.OidcTokenExchange,
|
||||
ImprovedPerformance: improvedPerformanceListToDomain(req.ImprovedPerformance),
|
||||
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +31,7 @@ func systemFeaturesToPb(f *query.SystemFeatures) *feature_pb.GetSystemFeaturesRe
|
||||
OidcTokenExchange: featureSourceToFlagPb(&f.TokenExchange),
|
||||
Actions: featureSourceToFlagPb(&f.Actions),
|
||||
ImprovedPerformance: featureSourceToImprovedPerformanceFlagPb(&f.ImprovedPerformance),
|
||||
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +46,7 @@ func instanceFeaturesToCommand(req *feature_pb.SetInstanceFeaturesRequest) *comm
|
||||
ImprovedPerformance: improvedPerformanceListToDomain(req.ImprovedPerformance),
|
||||
WebKey: req.WebKey,
|
||||
DebugOIDCParentError: req.DebugOidcParentError,
|
||||
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,6 +62,7 @@ func instanceFeaturesToPb(f *query.InstanceFeatures) *feature_pb.GetInstanceFeat
|
||||
ImprovedPerformance: featureSourceToImprovedPerformanceFlagPb(&f.ImprovedPerformance),
|
||||
WebKey: featureSourceToFlagPb(&f.WebKey),
|
||||
DebugOidcParentError: featureSourceToFlagPb(&f.DebugOIDCParentError),
|
||||
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ func Test_systemFeaturesToCommand(t *testing.T) {
|
||||
Actions: gu.Ptr(true),
|
||||
OidcTokenExchange: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
OidcSingleV1SessionTermination: gu.Ptr(true),
|
||||
}
|
||||
want := &command.SystemFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
@ -34,6 +35,7 @@ func Test_systemFeaturesToCommand(t *testing.T) {
|
||||
Actions: gu.Ptr(true),
|
||||
TokenExchange: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||
}
|
||||
got := systemFeaturesToCommand(arg)
|
||||
assert.Equal(t, want, got)
|
||||
@ -74,6 +76,10 @@ func Test_systemFeaturesToPb(t *testing.T) {
|
||||
Level: feature.LevelSystem,
|
||||
Value: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgByID},
|
||||
},
|
||||
OIDCSingleV1SessionTermination: query.FeatureSource[bool]{
|
||||
Level: feature.LevelSystem,
|
||||
Value: true,
|
||||
},
|
||||
}
|
||||
want := &feature_pb.GetSystemFeaturesResponse{
|
||||
Details: &object.Details{
|
||||
@ -109,6 +115,10 @@ func Test_systemFeaturesToPb(t *testing.T) {
|
||||
ExecutionPaths: []feature_pb.ImprovedPerformance{feature_pb.ImprovedPerformance_IMPROVED_PERFORMANCE_ORG_BY_ID},
|
||||
Source: feature_pb.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
OidcSingleV1SessionTermination: &feature_pb.FeatureFlag{
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
}
|
||||
got := systemFeaturesToPb(arg)
|
||||
assert.Equal(t, want, got)
|
||||
@ -124,6 +134,7 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
||||
Actions: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
WebKey: gu.Ptr(true),
|
||||
OidcSingleV1SessionTermination: gu.Ptr(true),
|
||||
}
|
||||
want := &command.InstanceFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
@ -134,6 +145,7 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
||||
Actions: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
WebKey: gu.Ptr(true),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||
}
|
||||
got := instanceFeaturesToCommand(arg)
|
||||
assert.Equal(t, want, got)
|
||||
@ -178,6 +190,10 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
},
|
||||
OIDCSingleV1SessionTermination: query.FeatureSource[bool]{
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
},
|
||||
}
|
||||
want := &feature_pb.GetInstanceFeaturesResponse{
|
||||
Details: &object.Details{
|
||||
@ -221,6 +237,10 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
Enabled: false,
|
||||
Source: feature_pb.Source_SOURCE_UNSPECIFIED,
|
||||
},
|
||||
OidcSingleV1SessionTermination: &feature_pb.FeatureFlag{
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_INSTANCE,
|
||||
},
|
||||
}
|
||||
got := instanceFeaturesToPb(arg)
|
||||
assert.Equal(t, want, got)
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
http_utils "github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||
"github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/handler"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
@ -245,11 +246,20 @@ func (o *OPStorage) TerminateSessionFromRequest(ctx context.Context, endSessionR
|
||||
}
|
||||
|
||||
// If there is no login client header and no id_token_hint or the id_token_hint does not have a session ID,
|
||||
// do a v1 Terminate session.
|
||||
// do a v1 Terminate session (which terminates all sessions of the user agent, identified by cookie).
|
||||
if endSessionRequest.IDTokenHintClaims == nil || endSessionRequest.IDTokenHintClaims.SessionID == "" {
|
||||
return endSessionRequest.RedirectURI, o.TerminateSession(ctx, endSessionRequest.UserID, endSessionRequest.ClientID)
|
||||
}
|
||||
|
||||
// If the sessionID is prefixed by V1, we also terminate a v1 session.
|
||||
if strings.HasPrefix(endSessionRequest.IDTokenHintClaims.SessionID, handler.IDPrefixV1) {
|
||||
err = o.terminateV1Session(ctx, endSessionRequest.UserID, endSessionRequest.IDTokenHintClaims.SessionID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return endSessionRequest.RedirectURI, nil
|
||||
}
|
||||
|
||||
// terminate the v2 session of the id_token_hint
|
||||
_, err = o.command.TerminateSessionWithoutTokenCheck(ctx, endSessionRequest.IDTokenHintClaims.SessionID)
|
||||
if err != nil {
|
||||
@ -258,6 +268,30 @@ func (o *OPStorage) TerminateSessionFromRequest(ctx context.Context, endSessionR
|
||||
return endSessionRequest.RedirectURI, nil
|
||||
}
|
||||
|
||||
// terminateV1Session terminates "v1" sessions created through the login UI.
|
||||
// Depending on the flag, we either terminate a single session or all of the user agent
|
||||
func (o *OPStorage) terminateV1Session(ctx context.Context, userID, sessionID string) error {
|
||||
ctx = authz.SetCtxData(ctx, authz.CtxData{UserID: userID})
|
||||
// if the flag is active we only terminate the specific session
|
||||
if authz.GetFeatures(ctx).OIDCSingleV1SessionTermination {
|
||||
userAgentID, err := o.repo.UserAgentIDBySessionID(ctx, sessionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return o.command.HumansSignOut(ctx, userAgentID, []string{userID})
|
||||
}
|
||||
// otherwise we search for all active sessions within the same user agent of the current session id
|
||||
userAgentID, userIDs, err := o.repo.ActiveUserIDsBySessionID(ctx, sessionID)
|
||||
if err != nil {
|
||||
logging.WithError(err).Error("error retrieving user sessions")
|
||||
return err
|
||||
}
|
||||
if len(userIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return o.command.HumansSignOut(ctx, userAgentID, userIDs)
|
||||
}
|
||||
|
||||
func (o *OPStorage) RevokeToken(ctx context.Context, token, userID, clientID string) (err *oidc.Error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() {
|
||||
@ -564,6 +598,7 @@ func (s *Server) authResponseToken(authReq *AuthRequest, authorizer op.Authorize
|
||||
domain.TokenReasonAuthRequest,
|
||||
nil,
|
||||
slices.Contains(scope, oidc.ScopeOfflineAccess),
|
||||
authReq.SessionID,
|
||||
)
|
||||
if err != nil {
|
||||
op.AuthRequestError(w, r, authReq, err, authorizer)
|
||||
|
@ -45,6 +45,7 @@ func (s *Server) ClientCredentialsExchange(ctx context.Context, r *op.ClientRequ
|
||||
domain.TokenReasonClientCredentials,
|
||||
nil,
|
||||
false,
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -85,6 +85,7 @@ func (s *Server) codeExchangeV1(ctx context.Context, client *Client, req *oidc.A
|
||||
domain.TokenReasonAuthRequest,
|
||||
nil,
|
||||
slices.Contains(scope, oidc.ScopeOfflineAccess),
|
||||
authReq.SessionID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -298,6 +298,7 @@ func (s *Server) createExchangeAccessToken(
|
||||
reason,
|
||||
actor,
|
||||
slices.Contains(scope, oidc.ScopeOfflineAccess),
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
return "", "", "", 0, err
|
||||
@ -342,6 +343,7 @@ func (s *Server) createExchangeJWT(
|
||||
reason,
|
||||
actor,
|
||||
slices.Contains(scope, oidc.ScopeOfflineAccess),
|
||||
"",
|
||||
)
|
||||
accessToken, err = s.createJWT(ctx, client, session, getUserInfo, roleAssertion, getSigner)
|
||||
if err != nil {
|
||||
|
@ -53,6 +53,7 @@ func (s *Server) JWTProfile(ctx context.Context, r *op.Request[oidc.JWTProfileGr
|
||||
domain.TokenReasonJWTProfile,
|
||||
nil,
|
||||
false,
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -67,6 +67,7 @@ func (s *Server) refreshTokenV1(ctx context.Context, client *Client, r *op.Clien
|
||||
domain.TokenReasonRefresh,
|
||||
refreshToken.Actor,
|
||||
true,
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -162,7 +162,7 @@ func (l *Login) handleDeviceAuthAction(w http.ResponseWriter, r *http.Request) {
|
||||
action := mux.Vars(r)["action"]
|
||||
switch action {
|
||||
case deviceAuthAllowed:
|
||||
_, err = l.command.ApproveDeviceAuth(r.Context(), authDev.DeviceCode, authReq.UserID, authReq.UserOrgID, authReq.UserAuthMethodTypes(), authReq.AuthTime, authReq.PreferredLanguage, authReq.ToUserAgent())
|
||||
_, err = l.command.ApproveDeviceAuth(r.Context(), authDev.DeviceCode, authReq.UserID, authReq.UserOrgID, authReq.UserAuthMethodTypes(), authReq.AuthTime, authReq.PreferredLanguage, authReq.ToUserAgent(), authReq.SessionID)
|
||||
case deviceAuthDenied:
|
||||
_, err = l.command.CancelDeviceAuth(r.Context(), authDev.DeviceCode, domain.DeviceAuthCanceledDenied)
|
||||
default:
|
||||
|
@ -250,6 +250,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Пол
|
||||
Female: Женски пол
|
||||
Male: Мъжки
|
||||
@ -289,6 +290,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Правила и условия
|
||||
TosConfirm: Приемам
|
||||
TosLinkText: TOS
|
||||
@ -357,6 +359,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
DeviceAuth:
|
||||
Title: Упълномощаване на устройството
|
||||
UserCode:
|
||||
|
@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Pohlaví
|
||||
Female: Žena
|
||||
Male: Muž
|
||||
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Obchodní podmínky
|
||||
TosConfirm: Souhlasím s
|
||||
TosLinkText: obchodními podmínkami
|
||||
@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
DeviceAuth:
|
||||
Title: Autorizace zařízení
|
||||
UserCode:
|
||||
|
@ -252,6 +252,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Geschlecht
|
||||
Female: weiblich
|
||||
Male: männlich
|
||||
@ -292,6 +293,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Allgemeine Geschäftsbedingungen und Datenschutz
|
||||
TosConfirm: Ich akzeptiere die
|
||||
TosLinkText: AGB
|
||||
@ -366,6 +368,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
DeviceAuth:
|
||||
Title: Gerät verbinden
|
||||
UserCode:
|
||||
|
@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Gender
|
||||
Female: Female
|
||||
Male: Male
|
||||
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Terms and conditions
|
||||
TosConfirm: I accept the
|
||||
TosLinkText: TOS
|
||||
@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
DeviceAuth:
|
||||
Title: Device Authorization
|
||||
UserCode:
|
||||
|
@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Género
|
||||
Female: Mujer
|
||||
Male: Hombre
|
||||
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Términos y condiciones
|
||||
TosConfirm: Acepto los
|
||||
TosLinkText: TDS
|
||||
@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
|
||||
Footer:
|
||||
PoweredBy: Powered By
|
||||
|
@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Genre
|
||||
Female: Femme
|
||||
Male: Homme
|
||||
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Termes et conditions
|
||||
TosConfirm: J'accepte les
|
||||
TosLinkText: TOS
|
||||
@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
|
||||
DeviceAuth:
|
||||
Title: Autorisation de l'appareil
|
||||
|
466
internal/api/ui/login/static/i18n/id.yaml
Normal file
466
internal/api/ui/login/static/i18n/id.yaml
Normal file
@ -0,0 +1,466 @@
|
||||
Login:
|
||||
Title: Selamat Datang kembali!
|
||||
Description: Masukkan data masuk Anda.
|
||||
TitleLinking: Login untuk menghubungkan pengguna
|
||||
DescriptionLinking: Masukkan data login Anda untuk menghubungkan pengguna eksternal Anda.
|
||||
LoginNameLabel: Nama Masuk
|
||||
UsernamePlaceHolder: nama belakang
|
||||
LoginnamePlaceHolder: nama pengguna@domain
|
||||
ExternalUserDescription: Masuk dengan pengguna eksternal.
|
||||
MustBeMemberOfOrg: 'Pengguna harus menjadi anggota {{.OrgName}} organisasi.'
|
||||
RegisterButtonText: Daftar
|
||||
NextButtonText: Berikutnya
|
||||
LDAP:
|
||||
Title: Login
|
||||
Description: Masukkan data masuk Anda.
|
||||
LoginNameLabel: Nama Masuk
|
||||
PasswordLabel: Kata sandi
|
||||
NextButtonText: Berikutnya
|
||||
SelectAccount:
|
||||
Title: Pilih Akun
|
||||
Description: Gunakan akun Anda
|
||||
TitleLinking: Pilih akun untuk penautan pengguna
|
||||
DescriptionLinking: Pilih akun Anda untuk ditautkan dengan pengguna eksternal Anda.
|
||||
OtherUser: Pengguna Lain
|
||||
SessionState0: aktif
|
||||
SessionState1: Keluar
|
||||
MustBeMemberOfOrg: 'Pengguna harus menjadi anggota {{.OrgName}} organisasi.'
|
||||
Password:
|
||||
Title: Kata sandi
|
||||
Description: Masukkan data masuk Anda.
|
||||
PasswordLabel: Kata sandi
|
||||
MinLength: Setidaknya harus begitu
|
||||
MinLengthp2: karakter panjang.
|
||||
MaxLength: Panjangnya harus kurang dari 70 karakter.
|
||||
HasUppercase: Harus menyertakan huruf besar.
|
||||
HasLowercase: Harus menyertakan huruf kecil.
|
||||
HasNumber: Harus menyertakan nomor.
|
||||
HasSymbol: Harus menyertakan simbol.
|
||||
Confirmation: Konfirmasi kata sandi cocok.
|
||||
ResetLinkText: Atur Ulang Kata Sandi
|
||||
BackButtonText: Kembali
|
||||
NextButtonText: Berikutnya
|
||||
UsernameChange:
|
||||
Title: Ubah Nama Pengguna
|
||||
Description: Tetapkan nama pengguna baru Anda
|
||||
UsernameLabel: Nama belakang
|
||||
CancelButtonText: Membatalkan
|
||||
NextButtonText: Berikutnya
|
||||
UsernameChangeDone:
|
||||
Title: Nama Pengguna Berubah
|
||||
Description: Nama pengguna Anda berhasil diubah.
|
||||
NextButtonText: Berikutnya
|
||||
InitPassword:
|
||||
Title: Tetapkan Kata Sandi
|
||||
Description: Anda telah menerima kode, yang harus Anda masukkan pada formulir di bawah ini, untuk mengatur kata sandi baru Anda.
|
||||
CodeLabel: Kode
|
||||
NewPasswordLabel: Kata Sandi Baru
|
||||
NewPasswordConfirmLabel: Konfirmasi Kata Sandi
|
||||
ResendButtonText: Kirim Ulang Kode
|
||||
NextButtonText: Berikutnya
|
||||
InitPasswordDone:
|
||||
Title: Kumpulan Kata Sandi
|
||||
Description: Kata sandi berhasil disetel
|
||||
NextButtonText: Berikutnya
|
||||
CancelButtonText: Membatalkan
|
||||
InitUser:
|
||||
Title: Aktifkan Pengguna
|
||||
Description: Verifikasi email Anda dengan kode di bawah ini dan atur kata sandi Anda.
|
||||
CodeLabel: Kode
|
||||
NewPasswordLabel: Kata Sandi Baru
|
||||
NewPasswordConfirm: Konfirmasi Kata Sandi
|
||||
NextButtonText: Berikutnya
|
||||
ResendButtonText: Kirim Ulang Kode
|
||||
InitUserDone:
|
||||
Title: Pengguna Diaktifkan
|
||||
Description: Email terverifikasi dan Kata Sandi berhasil ditetapkan
|
||||
NextButtonText: Berikutnya
|
||||
CancelButtonText: Membatalkan
|
||||
InitMFAPrompt:
|
||||
Title: Pengaturan 2 Faktor
|
||||
Description: Otentikasi 2 faktor memberi Anda keamanan tambahan untuk akun pengguna Anda.
|
||||
Provider0: 'Aplikasi Authenticator (misalnya Google/Microsoft Authenticator, Authy)'
|
||||
Provider1: 'Tergantung pada perangkat (misalnya FaceID, Windows Hello, Fingerprint)'
|
||||
Provider3: SMS OTP
|
||||
Provider4: Email OTP
|
||||
NextButtonText: Berikutnya
|
||||
SkipButtonText: Melewati
|
||||
InitMFAOTP:
|
||||
Title: Verifikasi 2 Faktor
|
||||
Description: 'Buat 2 faktor Anda. '
|
||||
OTPDescription: Pindai kode dengan aplikasi autentikator Anda (misalnya Google/Microsoft Authenticator, Authy) atau salin rahasianya dan masukkan kode yang dihasilkan di bawah.
|
||||
SecretLabel: Rahasia
|
||||
CodeLabel: Kode
|
||||
NextButtonText: Berikutnya
|
||||
CancelButtonText: Membatalkan
|
||||
InitMFAOTPSMS:
|
||||
Title: Verifikasi 2 Faktor
|
||||
DescriptionPhone: 'Buat 2 faktor Anda. '
|
||||
DescriptionCode: 'Buat 2 faktor Anda. '
|
||||
PhoneLabel: Telepon
|
||||
CodeLabel: Kode
|
||||
EditButtonText: Sunting
|
||||
ResendButtonText: Kirim Ulang Kode
|
||||
NextButtonText: Berikutnya
|
||||
InitMFAU2F:
|
||||
Title: Tambahkan Kunci Keamanan
|
||||
Description: Kunci keamanan adalah metode verifikasi yang dapat dipasang di ponsel Anda, menggunakan Bluetooth, atau dicolokkan langsung ke port USB komputer Anda.
|
||||
TokenNameLabel: Nama kunci keamanan/perangkat
|
||||
NotSupported: 'WebAuthN tidak didukung oleh browser Anda. '
|
||||
RegisterTokenButtonText: Tambahkan kunci keamanan
|
||||
ErrorRetry: 'Coba lagi, buat tantangan baru atau pilih metode lain.'
|
||||
InitMFADone:
|
||||
Title: Terverifikasi 2 faktor
|
||||
Description: 'Luar biasa! '
|
||||
NextButtonText: Berikutnya
|
||||
CancelButtonText: Membatalkan
|
||||
MFAProvider:
|
||||
Provider0: 'Aplikasi Authenticator (misalnya Google/Microsoft Authenticator, Authy)'
|
||||
Provider1: 'Tergantung pada perangkat (misalnya FaceID, Windows Hello, Fingerprint)'
|
||||
Provider3: SMS OTP
|
||||
Provider4: Email OTP
|
||||
ChooseOther: atau pilih opsi lain
|
||||
VerifyMFAOTP:
|
||||
Title: Verifikasi 2 Faktor
|
||||
Description: Verifikasi faktor kedua Anda
|
||||
CodeLabel: Kode
|
||||
NextButtonText: Berikutnya
|
||||
VerifyOTP:
|
||||
Title: Verifikasi 2 Faktor
|
||||
Description: Verifikasi faktor kedua Anda
|
||||
CodeLabel: Kode
|
||||
ResendButtonText: Kirim Ulang Kode
|
||||
NextButtonText: Berikutnya
|
||||
VerifyMFAU2F:
|
||||
Title: Verifikasi 2 Faktor
|
||||
Description: Verifikasi 2-Faktor Anda dengan perangkat yang terdaftar (misalnya FaceID, Windows Hello, Fingerprint)
|
||||
NotSupported: 'WebAuthN tidak didukung oleh browser Anda. '
|
||||
ErrorRetry: 'Coba lagi, buat permintaan baru atau pilih metode lain.'
|
||||
ValidateTokenButtonText: Verifikasi 2 Faktor
|
||||
Passwordless:
|
||||
Title: Masuk Tanpa Kata Sandi
|
||||
Description: Masuk dengan metode autentikasi yang disediakan oleh perangkat Anda seperti FaceID, Windows Hello, atau Sidik Jari.
|
||||
NotSupported: 'WebAuthN tidak didukung oleh browser Anda. '
|
||||
ErrorRetry: 'Coba lagi, buat tantangan baru atau pilih metode lain.'
|
||||
LoginWithPwButtonText: Masuk dengan kata sandi
|
||||
ValidateTokenButtonText: Masuk dengan tanpa kata sandi
|
||||
PasswordlessPrompt:
|
||||
Title: Pengaturan Tanpa Kata Sandi
|
||||
Description: 'Apakah Anda ingin mengatur login tanpa kata sandi? '
|
||||
DescriptionInit: 'Anda perlu mengatur login tanpa kata sandi. '
|
||||
PasswordlessButtonText: Tanpa kata sandi
|
||||
NextButtonText: Berikutnya
|
||||
SkipButtonText: Melewati
|
||||
PasswordlessRegistration:
|
||||
Title: Pengaturan Tanpa Kata Sandi
|
||||
Description: Tambahkan otentikasi Anda dengan memberikan nama (misalnya MyMobilePhone, MacBook, dll) dan kemudian mengklik tombol 'Daftar tanpa kata sandi' di bawah.
|
||||
TokenNameLabel: Nama perangkat
|
||||
NotSupported: 'WebAuthN tidak didukung oleh browser Anda. '
|
||||
RegisterTokenButtonText: Daftar tanpa kata sandi
|
||||
ErrorRetry: 'Coba lagi, buat tantangan baru atau pilih metode lain.'
|
||||
PasswordlessRegistrationDone:
|
||||
Title: Pengaturan Tanpa Kata Sandi
|
||||
Description: Perangkat tanpa kata sandi berhasil ditambahkan.
|
||||
DescriptionClose: Anda sekarang dapat menutup jendela ini.
|
||||
NextButtonText: Berikutnya
|
||||
CancelButtonText: Membatalkan
|
||||
PasswordChange:
|
||||
Title: Ubah Kata Sandi
|
||||
Description: 'Ubah kata sandi Anda. '
|
||||
ExpiredDescription: 'Kata sandi Anda telah kedaluwarsa dan harus diubah. '
|
||||
OldPasswordLabel: Kata Sandi Lama
|
||||
NewPasswordLabel: Kata Sandi Baru
|
||||
NewPasswordConfirmLabel: Konfirmasi kata sandi
|
||||
CancelButtonText: Membatalkan
|
||||
NextButtonText: Berikutnya
|
||||
Footer: catatan kaki
|
||||
PasswordChangeDone:
|
||||
Title: Ubah Kata Sandi
|
||||
Description: Kata sandi Anda berhasil diubah.
|
||||
NextButtonText: Berikutnya
|
||||
PasswordResetDone:
|
||||
Title: Tautan Reset Kata Sandi Terkirim
|
||||
Description: Periksa email Anda untuk mengatur ulang kata sandi Anda.
|
||||
NextButtonText: Berikutnya
|
||||
EmailVerification:
|
||||
Title: Verifikasi Email
|
||||
Description: 'Kami telah mengirimi Anda email untuk memverifikasi alamat Anda. '
|
||||
CodeLabel: Kode
|
||||
NextButtonText: Berikutnya
|
||||
ResendButtonText: Kirim Ulang Kode
|
||||
EmailVerificationDone:
|
||||
Title: Verifikasi Email
|
||||
Description: Alamat email Anda telah berhasil diverifikasi.
|
||||
NextButtonText: Berikutnya
|
||||
CancelButtonText: Membatalkan
|
||||
LoginButtonText: Login
|
||||
RegisterOption:
|
||||
Title: Opsi Pendaftaran
|
||||
Description: Pilih bagaimana Anda ingin mendaftar
|
||||
RegisterUsernamePasswordButtonText: Dengan nama pengguna dan kata sandi
|
||||
ExternalLoginDescription: atau mendaftar dengan pengguna eksternal
|
||||
LoginButtonText: Login
|
||||
RegistrationUser:
|
||||
Title: Pendaftaran
|
||||
Description: 'Masukkan Data Pengguna Anda. '
|
||||
DescriptionOrgRegister: Masukkan Data Pengguna Anda.
|
||||
EmailLabel: E-mail
|
||||
UsernameLabel: Nama belakang
|
||||
FirstnameLabel: Nama yang diberikan
|
||||
LastnameLabel: Nama keluarga
|
||||
LanguageLabel: Bahasa
|
||||
German: Deutsch
|
||||
English: English
|
||||
Italian: Italiano
|
||||
French: Français
|
||||
Chinese: 简体中文
|
||||
Polish: Polski
|
||||
Japanese: 日本語
|
||||
Spanish: Español
|
||||
Bulgarian: Български
|
||||
Portuguese: Português
|
||||
Macedonian: Македонски
|
||||
Czech: Čeština
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Jenis kelamin
|
||||
Female: Perempuan
|
||||
Male: Pria
|
||||
Diverse: beragam / X
|
||||
PasswordLabel: Kata sandi
|
||||
PasswordConfirmLabel: Konfirmasi kata sandi
|
||||
TosAndPrivacyLabel: Syarat dan Ketentuan
|
||||
TosConfirm: Saya menerima itu
|
||||
TosLinkText: KL
|
||||
PrivacyConfirm: Saya menerima itu
|
||||
PrivacyLinkText: kebijakan privasi
|
||||
ExternalLogin: atau mendaftar dengan pengguna eksternal
|
||||
BackButtonText: Login
|
||||
NextButtonText: Berikutnya
|
||||
ExternalRegistrationUserOverview:
|
||||
Title: Registrasi Pengguna Eksternal
|
||||
Description: 'Kami telah mengambil detail pengguna Anda dari penyedia yang dipilih. '
|
||||
EmailLabel: E-mail
|
||||
UsernameLabel: Nama belakang
|
||||
FirstnameLabel: Nama yang diberikan
|
||||
LastnameLabel: Nama keluarga
|
||||
NicknameLabel: Nama panggilan
|
||||
PhoneLabel: Nomor telepon
|
||||
LanguageLabel: Bahasa
|
||||
German: Deutsch
|
||||
English: English
|
||||
Italian: Italiano
|
||||
French: Français
|
||||
Chinese: 简体中文
|
||||
Polish: Polski
|
||||
Japanese: 日本語
|
||||
Spanish: Español
|
||||
Bulgarian: Български
|
||||
Portuguese: Português
|
||||
Macedonian: Македонски
|
||||
Czech: Čeština
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Syarat dan Ketentuan
|
||||
TosConfirm: Saya menerima itu
|
||||
TosLinkText: KL
|
||||
PrivacyConfirm: Saya menerima itu
|
||||
PrivacyLinkText: kebijakan privasi
|
||||
ExternalLogin: atau mendaftar dengan pengguna eksternal
|
||||
BackButtonText: tidak
|
||||
NextButtonText: Menyimpan
|
||||
RegistrationOrg:
|
||||
Title: Pendaftaran Organisasi
|
||||
Description: Masukkan nama organisasi dan data pengguna Anda.
|
||||
OrgNameLabel: Nama organisasi
|
||||
EmailLabel: E-mail
|
||||
UsernameLabel: Nama belakang
|
||||
FirstnameLabel: Nama yang diberikan
|
||||
LastnameLabel: Nama keluarga
|
||||
PasswordLabel: Kata sandi
|
||||
PasswordConfirmLabel: Konfirmasi kata sandi
|
||||
TosAndPrivacyLabel: Syarat dan Ketentuan
|
||||
TosConfirm: Saya menerima itu
|
||||
TosLinkText: KL
|
||||
PrivacyConfirm: Saya menerima itu
|
||||
PrivacyLinkText: kebijakan privasi
|
||||
SaveButtonText: Buat organisasi
|
||||
LoginSuccess:
|
||||
Title: Masuk Berhasil
|
||||
AutoRedirectDescription: 'Anda akan diarahkan kembali ke aplikasi Anda secara otomatis. '
|
||||
RedirectedDescription: Anda sekarang dapat menutup jendela ini.
|
||||
NextButtonText: Berikutnya
|
||||
LogoutDone:
|
||||
Title: Keluar
|
||||
Description: Anda telah berhasil logout.
|
||||
LoginButtonText: Login
|
||||
LinkingUserPrompt:
|
||||
Title: Pengguna Lama Ditemukan
|
||||
Description: 'Apakah Anda ingin menautkan akun Anda yang ada:'
|
||||
LinkButtonText: Link
|
||||
OtherButtonText: Pilihan lain
|
||||
LinkingUsersDone:
|
||||
Title: Menghubungkan Pengguna
|
||||
Description: Tertaut pengguna.
|
||||
CancelButtonText: Membatalkan
|
||||
NextButtonText: Berikutnya
|
||||
ExternalNotFound:
|
||||
Title: Pengguna Eksternal Tidak Ditemukan
|
||||
Description: 'Pengguna eksternal tidak ditemukan. '
|
||||
LinkButtonText: Link
|
||||
AutoRegisterButtonText: Daftar
|
||||
TosAndPrivacyLabel: Syarat dan Ketentuan
|
||||
TosConfirm: Saya menerima itu
|
||||
TosLinkText: KL
|
||||
PrivacyConfirm: Saya menerima itu
|
||||
PrivacyLinkText: kebijakan privasi
|
||||
German: Deutsch
|
||||
English: English
|
||||
Italian: Italiano
|
||||
French: Français
|
||||
Chinese: 简体中文
|
||||
Polish: Polski
|
||||
Japanese: 日本語
|
||||
Spanish: Español
|
||||
Bulgarian: Български
|
||||
Portuguese: Português
|
||||
Macedonian: Македонски
|
||||
Czech: Čeština
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
DeviceAuth:
|
||||
Title: Otorisasi Perangkat
|
||||
UserCode:
|
||||
Label: Kode Pengguna
|
||||
Description: Masukkan kode pengguna yang disajikan pada perangkat.
|
||||
ButtonNext: Berikutnya
|
||||
Action:
|
||||
Description: Berikan akses perangkat.
|
||||
GrantDevice: Anda akan memberikan perangkat
|
||||
AccessToScopes: akses ke cakupan berikut
|
||||
Button:
|
||||
Allow: Mengizinkan
|
||||
Deny: Membantah
|
||||
Done:
|
||||
Description: Selesai.
|
||||
Approved: 'Otorisasi perangkat disetujui. '
|
||||
Denied: 'Otorisasi perangkat ditolak. '
|
||||
Footer:
|
||||
PoweredBy: Didukung oleh
|
||||
Tos: KL
|
||||
PrivacyPolicy: Kebijakan privasi
|
||||
Help: Membantu
|
||||
SupportEmail: Email Dukungan
|
||||
SignIn: 'Masuk dengan {{.Provider}}'
|
||||
Errors:
|
||||
Internal: Terjadi kesalahan internal
|
||||
AuthRequest:
|
||||
NotFound: Tidak dapat menemukan permintaan autentikasi
|
||||
UserAgentNotCorresponding: Agen Pengguna tidak sesuai
|
||||
UserAgentNotFound: ID Agen Pengguna tidak ditemukan
|
||||
TokenNotFound: Token tidak ditemukan
|
||||
RequestTypeNotSupported: Jenis permintaan tidak didukung
|
||||
MissingParameters: Parameter yang diperlukan tidak ada
|
||||
User:
|
||||
NotFound: Pengguna tidak dapat ditemukan
|
||||
AlreadyExists: Pengguna sudah ada
|
||||
Inactive: Pengguna tidak aktif
|
||||
NotFoundOnOrg: Pengguna tidak dapat ditemukan di organisasi yang dipilih
|
||||
NotAllowedOrg: Pengguna bukan anggota organisasi yang diperlukan
|
||||
NotMatchingUserID: Pengguna dan pengguna dalam permintaan authrequest tidak cocok
|
||||
UserIDMissing: ID Pengguna kosong
|
||||
Invalid: Data pengguna tidak valid
|
||||
DomainNotAllowedAsUsername: Domain sudah dipesan dan tidak dapat digunakan
|
||||
NotAllowedToLink: Pengguna tidak diperbolehkan terhubung dengan penyedia login eksternal
|
||||
Profile:
|
||||
NotFound: Profil tidak ditemukan
|
||||
NotChanged: Profil tidak berubah
|
||||
Empty: Profil kosong
|
||||
FirstNameEmpty: Nama depan di profil kosong
|
||||
LastNameEmpty: Nama keluarga di profil kosong
|
||||
IDMissing: ID Profil tidak ada
|
||||
Email:
|
||||
NotFound: Email tidak ditemukan
|
||||
Invalid: Email tidak valid
|
||||
AlreadyVerified: Email sudah diverifikasi
|
||||
NotChanged: Email tidak diubah
|
||||
Empty: Emailnya kosong
|
||||
IDMissing: ID email tidak ada
|
||||
Phone:
|
||||
NotFound: Telepon tidak ditemukan
|
||||
Invalid: Telepon tidak valid
|
||||
AlreadyVerified: Telepon sudah diverifikasi
|
||||
Empty: Telepon kosong
|
||||
NotChanged: Telepon tidak berubah
|
||||
Address:
|
||||
NotFound: Alamat tidak ditemukan
|
||||
NotChanged: Alamat tidak diubah
|
||||
Username:
|
||||
AlreadyExists: Nama pengguna sudah dipakai
|
||||
Reserved: Nama pengguna sudah dipakai
|
||||
Empty: Nama pengguna kosong
|
||||
Password:
|
||||
ConfirmationWrong: Konfirmasi kata sandi salah
|
||||
Empty: Kata sandi kosong
|
||||
Invalid: Kata sandi tidak valid
|
||||
InvalidAndLocked: Kata sandi tidak valid dan pengguna terkunci, hubungi administrator Anda.
|
||||
NotChanged: Kata sandi baru tidak boleh sama dengan kata sandi Anda saat ini
|
||||
UsernameOrPassword:
|
||||
Invalid: Nama Pengguna atau Kata Sandi tidak valid
|
||||
PasswordComplexityPolicy:
|
||||
NotFound: Kebijakan kata sandi tidak ditemukan
|
||||
MinLength: Kata sandi terlalu pendek
|
||||
HasLower: Kata sandi harus mengandung huruf kecil
|
||||
HasUpper: Kata sandi harus mengandung huruf besar
|
||||
HasNumber: Kata sandi harus berisi nomor
|
||||
HasSymbol: Kata sandi harus mengandung simbol
|
||||
Code:
|
||||
Expired: Kode sudah habis masa berlakunya
|
||||
Invalid: Kode tidak valid
|
||||
Empty: Kode kosong
|
||||
CryptoCodeNil: Kode kripto nihil
|
||||
NotFound: Tidak dapat menemukan kode
|
||||
GeneratorAlgNotSupported: Algoritme generator tidak didukung
|
||||
EmailVerify:
|
||||
UserIDEmpty: ID Pengguna kosong
|
||||
ExternalData:
|
||||
CouldNotRead: Data eksternal tidak dapat dibaca dengan benar
|
||||
MFA:
|
||||
NoProviders: Tidak ada penyedia multifaktor yang tersedia
|
||||
OTP:
|
||||
AlreadyReady: OTP multifaktor (OneTimePassword) sudah disiapkan
|
||||
NotExisting: OTP multifaktor (OneTimePassword) tidak ada
|
||||
InvalidCode: Kode tidak valid
|
||||
NotReady: OTP multifaktor (OneTimePassword) belum siap
|
||||
Locked: Pengguna terkunci
|
||||
SomethingWentWrong: Ada yang tidak beres
|
||||
NotActive: Pengguna tidak aktif
|
||||
ExternalIDP:
|
||||
IDPTypeNotImplemented: Tipe IDP tidak diterapkan
|
||||
NotAllowed: Penyedia Login Eksternal tidak diizinkan
|
||||
IDPConfigIDEmpty: ID Penyedia Identitas kosong
|
||||
ExternalUserIDEmpty: ID Pengguna Eksternal kosong
|
||||
UserDisplayNameEmpty: Nama Tampilan Pengguna kosong
|
||||
NoExternalUserData: Tidak ada Data Pengguna eksternal yang diterima
|
||||
CreationNotAllowed: Pembuatan pengguna baru tidak diperbolehkan pada penyedia ini
|
||||
LinkingNotAllowed: Menautkan pengguna tidak diperbolehkan di penyedia ini
|
||||
NoOptionAllowed: 'Pembuatan tautan tidak diperbolehkan pada penyedia ini. '
|
||||
GrantRequired: 'Masuk tidak dapat dilakukan. '
|
||||
ProjectRequired: 'Masuk tidak dapat dilakukan. '
|
||||
IdentityProvider:
|
||||
InvalidConfig: Konfigurasi Penyedia Identitas tidak valid
|
||||
IAM:
|
||||
LockoutPolicy:
|
||||
NotExisting: Kebijakan Lockout tidak ada
|
||||
Org:
|
||||
LoginPolicy:
|
||||
RegistrationNotAllowed: Pendaftaran tidak diperbolehkan
|
||||
DeviceAuth:
|
||||
NotExisting: Kode Pengguna tidak ada
|
||||
optional: (opsional)
|
@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Genere
|
||||
Female: Femminile
|
||||
Male: Maschile
|
||||
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Termini di servizio
|
||||
TosConfirm: Accetto i
|
||||
TosLinkText: Termini di servizio
|
||||
@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
|
||||
DeviceAuth:
|
||||
Title: Autorizzazione del dispositivo
|
||||
|
@ -245,6 +245,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: 性別
|
||||
Female: 女性
|
||||
Male: 男性
|
||||
@ -285,6 +286,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: 利用規約
|
||||
TosConfirm: 私は利用規約を承諾します。
|
||||
TosLinkText: TOS
|
||||
@ -359,6 +361,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
|
||||
DeviceAuth:
|
||||
Title: デバイス認証
|
||||
|
@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Пол
|
||||
Female: Женски
|
||||
Male: Машки
|
||||
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Правила и услови
|
||||
TosConfirm: Се согласувам со
|
||||
TosLinkText: правилата за користење
|
||||
@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
|
||||
DeviceAuth:
|
||||
Title: Овластување преку уред
|
||||
|
@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Geslacht
|
||||
Female: Vrouw
|
||||
Male: Man
|
||||
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Nederlands: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Algemene voorwaarden
|
||||
TosConfirm: Ik accepteer de
|
||||
TosLinkText: AV
|
||||
@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
DeviceAuth:
|
||||
Title: Apparaat Autorisatie
|
||||
UserCode:
|
||||
|
@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Płeć
|
||||
Female: Kobieta
|
||||
Male: Mężczyzna
|
||||
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Warunki i zasady
|
||||
TosConfirm: Akceptuję
|
||||
TosLinkText: Warunki korzystania
|
||||
@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
|
||||
DeviceAuth:
|
||||
Title: Autoryzacja urządzenia
|
||||
|
@ -249,6 +249,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Gênero
|
||||
Female: Feminino
|
||||
Male: Masculino
|
||||
@ -289,6 +290,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Termos e condições
|
||||
TosConfirm: Eu aceito os
|
||||
TosLinkText: termos de serviço
|
||||
@ -363,6 +365,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
|
||||
DeviceAuth:
|
||||
Title: Autorização de dispositivo
|
||||
|
@ -252,6 +252,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Пол
|
||||
Female: Женский
|
||||
Male: Мужской
|
||||
@ -292,6 +293,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Условия использования
|
||||
TosConfirm: Я согласен с
|
||||
TosLinkText: Пользовательским соглашением
|
||||
@ -366,6 +368,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
|
||||
DeviceAuth:
|
||||
Title: Авторизация устройства
|
||||
|
@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: Kön
|
||||
Female: Man
|
||||
Male: Kvinna
|
||||
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: Användarvillkor
|
||||
TosConfirm: Jag accepterar
|
||||
TosLinkText: Användarvillkoren
|
||||
@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
DeviceAuth:
|
||||
Title: Tillgång från hårdvaruenhet
|
||||
UserCode:
|
||||
|
@ -253,6 +253,7 @@ RegistrationUser:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
GenderLabel: 性别
|
||||
Female: 女性
|
||||
Male: 男性
|
||||
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
TosAndPrivacyLabel: 条款和条款
|
||||
TosConfirm: 我接受
|
||||
TosLinkText: 服务条款
|
||||
@ -367,6 +369,7 @@ ExternalNotFound:
|
||||
Russian: Русский
|
||||
Dutch: Nederlands
|
||||
Swedish: Svenska
|
||||
Indonesian: Bahasa Indonesia
|
||||
DeviceAuth:
|
||||
Title: 设备授权
|
||||
UserCode:
|
||||
|
@ -72,6 +72,8 @@
|
||||
</option>
|
||||
<option value="fr" id="fr" {{if (selectedLanguage "fr")}} selected {{end}}>{{t "ExternalNotFound.French"}}
|
||||
</option>
|
||||
<option value="id" id="id" {{if (selectedLanguage "id")}} selected {{end}}>{{t "ExternalNotFound.Indonesian"}}
|
||||
</option>
|
||||
<option value="it" id="it" {{if (selectedLanguage "it")}} selected {{end}}>{{t "ExternalNotFound.Italian"}}
|
||||
</option>
|
||||
<option value="ja" id="ja" {{if (selectedLanguage "ja")}} selected {{end}}>{{t "ExternalNotFound.Japanese"}}
|
||||
|
@ -74,8 +74,8 @@ type privacyPolicyProvider interface {
|
||||
}
|
||||
|
||||
type userSessionViewProvider interface {
|
||||
UserSessionByIDs(string, string, string) (*user_view_model.UserSessionView, error)
|
||||
UserSessionsByAgentID(string, string) ([]*user_view_model.UserSessionView, error)
|
||||
UserSessionByIDs(context.Context, string, string, string) (*user_view_model.UserSessionView, error)
|
||||
UserSessionsByAgentID(context.Context, string, string) ([]*user_view_model.UserSessionView, error)
|
||||
GetLatestUserSessionSequence(ctx context.Context, instanceID string) (*query.CurrentState, error)
|
||||
}
|
||||
|
||||
@ -1048,6 +1048,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.SessionID = userSession.ID
|
||||
request.DisplayName = userSession.DisplayName
|
||||
request.AvatarKey = userSession.AvatarKey
|
||||
if user.HumanView != nil && user.HumanView.PreferredLanguage != "" {
|
||||
@ -1532,7 +1533,7 @@ func userSessionsByUserAgentID(ctx context.Context, provider userSessionViewProv
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
session, err := provider.UserSessionsByAgentID(agentID, instanceID)
|
||||
session, err := provider.UserSessionsByAgentID(ctx, agentID, instanceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1572,7 +1573,7 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve
|
||||
OnError(err).
|
||||
Errorf("could not get current sequence for userSessionByIDs")
|
||||
|
||||
session, err := provider.UserSessionByIDs(agentID, user.ID, instanceID)
|
||||
session, err := provider.UserSessionByIDs(ctx, agentID, user.ID, instanceID)
|
||||
if err != nil {
|
||||
if !zerrors.IsNotFound(err) {
|
||||
return nil, err
|
||||
|
@ -34,11 +34,11 @@ var (
|
||||
|
||||
type mockViewNoUserSession struct{}
|
||||
|
||||
func (m *mockViewNoUserSession) UserSessionByIDs(string, string, string) (*user_view_model.UserSessionView, error) {
|
||||
func (m *mockViewNoUserSession) UserSessionByIDs(context.Context, string, string, string) (*user_view_model.UserSessionView, error) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "id", "user session not found")
|
||||
}
|
||||
|
||||
func (m *mockViewNoUserSession) UserSessionsByAgentID(string, string) ([]*user_view_model.UserSessionView, error) {
|
||||
func (m *mockViewNoUserSession) UserSessionsByAgentID(context.Context, string, string) ([]*user_view_model.UserSessionView, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@ -48,11 +48,11 @@ func (m *mockViewNoUserSession) GetLatestUserSessionSequence(ctx context.Context
|
||||
|
||||
type mockViewErrUserSession struct{}
|
||||
|
||||
func (m *mockViewErrUserSession) UserSessionByIDs(string, string, string) (*user_view_model.UserSessionView, error) {
|
||||
func (m *mockViewErrUserSession) UserSessionByIDs(context.Context, string, string, string) (*user_view_model.UserSessionView, error) {
|
||||
return nil, zerrors.ThrowInternal(nil, "id", "internal error")
|
||||
}
|
||||
|
||||
func (m *mockViewErrUserSession) UserSessionsByAgentID(string, string) ([]*user_view_model.UserSessionView, error) {
|
||||
func (m *mockViewErrUserSession) UserSessionsByAgentID(context.Context, string, string) ([]*user_view_model.UserSessionView, error) {
|
||||
return nil, zerrors.ThrowInternal(nil, "id", "internal error")
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ type mockUser struct {
|
||||
SessionState domain.UserSessionState
|
||||
}
|
||||
|
||||
func (m *mockViewUserSession) UserSessionByIDs(string, string, string) (*user_view_model.UserSessionView, error) {
|
||||
func (m *mockViewUserSession) UserSessionByIDs(context.Context, string, string, string) (*user_view_model.UserSessionView, error) {
|
||||
return &user_view_model.UserSessionView{
|
||||
ExternalLoginVerification: sql.NullTime{Time: m.ExternalLoginVerification},
|
||||
PasswordlessVerification: sql.NullTime{Time: m.PasswordlessVerification},
|
||||
@ -86,7 +86,7 @@ func (m *mockViewUserSession) UserSessionByIDs(string, string, string) (*user_vi
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *mockViewUserSession) UserSessionsByAgentID(string, string) ([]*user_view_model.UserSessionView, error) {
|
||||
func (m *mockViewUserSession) UserSessionsByAgentID(context.Context, string, string) ([]*user_view_model.UserSessionView, error) {
|
||||
sessions := make([]*user_view_model.UserSessionView, len(m.Users))
|
||||
for i, user := range m.Users {
|
||||
sessions[i] = &user_view_model.UserSessionView{
|
||||
|
@ -28,7 +28,7 @@ func (repo *UserRepo) Health(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (repo *UserRepo) UserSessionUserIDsByAgentID(ctx context.Context, agentID string) ([]string, error) {
|
||||
userSessions, err := repo.View.UserSessionsByAgentID(agentID, authz.GetInstance(ctx).InstanceID())
|
||||
userSessions, err := repo.View.UserSessionsByAgentID(ctx, agentID, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -41,6 +41,14 @@ func (repo *UserRepo) UserSessionUserIDsByAgentID(ctx context.Context, agentID s
|
||||
return userIDs, nil
|
||||
}
|
||||
|
||||
func (repo *UserRepo) UserAgentIDBySessionID(ctx context.Context, sessionID string) (string, error) {
|
||||
return repo.View.UserAgentIDBySessionID(ctx, sessionID, authz.GetInstance(ctx).InstanceID())
|
||||
}
|
||||
|
||||
func (repo *UserRepo) ActiveUserIDsBySessionID(ctx context.Context, sessionID string) (userAgentID string, userIDs []string, err error) {
|
||||
return repo.View.ActiveUserIDsBySessionID(ctx, sessionID, authz.GetInstance(ctx).InstanceID())
|
||||
}
|
||||
|
||||
func (repo *UserRepo) UserEventsByID(ctx context.Context, id string, changeDate time.Time, eventTypes []eventstore.EventType) ([]eventstore.Event, error) {
|
||||
query, err := usr_view.UserByIDQuery(id, authz.GetInstance(ctx).InstanceID(), changeDate, eventTypes)
|
||||
if err != nil {
|
||||
|
@ -14,7 +14,7 @@ type UserSessionRepo struct {
|
||||
}
|
||||
|
||||
func (repo *UserSessionRepo) GetMyUserSessions(ctx context.Context) ([]*usr_model.UserSessionView, error) {
|
||||
userSessions, err := repo.View.UserSessionsByAgentID(authz.GetCtxData(ctx).AgentID, authz.GetInstance(ctx).InstanceID())
|
||||
userSessions, err := repo.View.UserSessionsByAgentID(ctx, authz.GetCtxData(ctx).AgentID, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
||||
handler2 "github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
||||
"github.com/zitadel/zitadel/internal/id"
|
||||
query2 "github.com/zitadel/zitadel/internal/query"
|
||||
)
|
||||
|
||||
@ -40,6 +41,7 @@ func Register(ctx context.Context, configs Config, view *view.View, queries *que
|
||||
configs.overwrite("UserSession"),
|
||||
view,
|
||||
queries,
|
||||
id.SonyFlakeGenerator(),
|
||||
))
|
||||
|
||||
projections = append(projections, newToken(ctx,
|
||||
|
@ -2,12 +2,14 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
||||
"github.com/zitadel/zitadel/internal/id"
|
||||
query2 "github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
@ -18,12 +20,15 @@ import (
|
||||
|
||||
const (
|
||||
userSessionTable = "auth.user_sessions"
|
||||
|
||||
IDPrefixV1 = "V1_"
|
||||
)
|
||||
|
||||
type UserSession struct {
|
||||
queries *query2.Queries
|
||||
view *auth_view.View
|
||||
es handler.EventStore
|
||||
queries *query2.Queries
|
||||
view *auth_view.View
|
||||
es handler.EventStore
|
||||
idGenerator id.Generator
|
||||
}
|
||||
|
||||
var _ handler.Projection = (*UserSession)(nil)
|
||||
@ -33,14 +38,16 @@ func newUserSession(
|
||||
config handler.Config,
|
||||
view *auth_view.View,
|
||||
queries *query2.Queries,
|
||||
idGenerator id.Generator,
|
||||
) *handler.Handler {
|
||||
return handler.NewHandler(
|
||||
ctx,
|
||||
&config,
|
||||
&UserSession{
|
||||
queries: queries,
|
||||
view: view,
|
||||
es: config.Eventstore,
|
||||
queries: queries,
|
||||
view: view,
|
||||
es: config.Eventstore,
|
||||
idGenerator: idGenerator,
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -187,7 +194,7 @@ func (s *UserSession) Reducers() []handler.AggregateReducer {
|
||||
}
|
||||
}
|
||||
|
||||
func sessionColumns(event eventstore.Event, columns ...handler.Column) ([]handler.Column, error) {
|
||||
func (u *UserSession) sessionColumns(event eventstore.Event, columns ...handler.Column) ([]handler.Column, error) {
|
||||
userAgent, err := agentIDFromSession(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -203,14 +210,34 @@ func sessionColumns(event eventstore.Event, columns ...handler.Column) ([]handle
|
||||
}, columns...), nil
|
||||
}
|
||||
|
||||
func (u *UserSession) sessionColumnsActivate(event eventstore.Event, columns ...handler.Column) ([]handler.Column, error) {
|
||||
sessionID, err := u.idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sessionID = IDPrefixV1 + sessionID
|
||||
columns = slices.Grow(columns, 2)
|
||||
columns = append(columns,
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
handler.NewCol(view_model.UserSessionKeyID,
|
||||
handler.OnlySetValueInCase(userSessionTable, sessionID,
|
||||
handler.ConditionOr(
|
||||
handler.ColumnChangedCondition(userSessionTable, view_model.UserSessionKeyState, domain.UserSessionStateTerminated, domain.UserSessionStateActive),
|
||||
handler.ColumnIsNullCondition(userSessionTable, view_model.UserSessionKeyID),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
return u.sessionColumns(event, columns...)
|
||||
}
|
||||
|
||||
func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err error) {
|
||||
// in case anything needs to be change here check if appendEvent function needs the change as well
|
||||
switch event.Type() {
|
||||
case user.UserV1PasswordCheckSucceededType,
|
||||
user.HumanPasswordCheckSucceededType:
|
||||
columns, err := sessionColumns(event,
|
||||
columns, err := u.sessionColumnsActivate(event,
|
||||
handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()),
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -218,9 +245,8 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
|
||||
case user.UserV1PasswordCheckFailedType,
|
||||
user.HumanPasswordCheckFailedType:
|
||||
columns, err := sessionColumns(event,
|
||||
columns, err := u.sessionColumnsActivate(event,
|
||||
handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}),
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -228,10 +254,9 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
|
||||
case user.UserV1MFAOTPCheckSucceededType,
|
||||
user.HumanMFAOTPCheckSucceededType:
|
||||
columns, err := sessionColumns(event,
|
||||
columns, err := u.sessionColumnsActivate(event,
|
||||
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, event.CreatedAt()),
|
||||
handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFATypeTOTP),
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -240,9 +265,8 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
case user.UserV1MFAOTPCheckFailedType,
|
||||
user.HumanMFAOTPCheckFailedType,
|
||||
user.HumanU2FTokenCheckFailedType:
|
||||
columns, err := sessionColumns(event,
|
||||
columns, err := u.sessionColumnsActivate(event,
|
||||
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, time.Time{}),
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -250,7 +274,7 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
|
||||
case user.UserV1SignedOutType,
|
||||
user.HumanSignedOutType:
|
||||
columns, err := sessionColumns(event,
|
||||
columns, err := u.sessionColumns(event,
|
||||
handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}),
|
||||
handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}),
|
||||
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, time.Time{}),
|
||||
@ -270,10 +294,9 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
columns, err := sessionColumns(event,
|
||||
columns, err := u.sessionColumnsActivate(event,
|
||||
handler.NewCol(view_model.UserSessionKeyExternalLoginVerification, event.CreatedAt()),
|
||||
handler.NewCol(view_model.UserSessionKeySelectedIDPConfigID, data.SelectedIDPConfigID),
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -285,10 +308,9 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
columns, err := sessionColumns(event,
|
||||
columns, err := u.sessionColumnsActivate(event,
|
||||
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, event.CreatedAt()),
|
||||
handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFATypeU2F),
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -300,11 +322,10 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
columns, err := sessionColumns(event,
|
||||
columns, err := u.sessionColumnsActivate(event,
|
||||
handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, event.CreatedAt()),
|
||||
handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, event.CreatedAt()),
|
||||
handler.NewCol(view_model.UserSessionKeyMultiFactorVerificationType, domain.MFATypeU2FUserVerification),
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -316,10 +337,9 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
columns, err := sessionColumns(event,
|
||||
columns, err := u.sessionColumnsActivate(event,
|
||||
handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}),
|
||||
handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, time.Time{}),
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -429,8 +449,7 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
},
|
||||
), nil
|
||||
case user.HumanRegisteredType:
|
||||
columns, err := sessionColumns(event,
|
||||
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
|
||||
columns, err := u.sessionColumnsActivate(event,
|
||||
handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()),
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -12,12 +12,20 @@ const (
|
||||
userSessionTable = "auth.user_sessions"
|
||||
)
|
||||
|
||||
func (v *View) UserSessionByIDs(agentID, userID, instanceID string) (*model.UserSessionView, error) {
|
||||
return view.UserSessionByIDs(v.client, agentID, userID, instanceID)
|
||||
func (v *View) UserSessionByIDs(ctx context.Context, agentID, userID, instanceID string) (*model.UserSessionView, error) {
|
||||
return view.UserSessionByIDs(ctx, v.client, agentID, userID, instanceID)
|
||||
}
|
||||
|
||||
func (v *View) UserSessionsByAgentID(agentID, instanceID string) ([]*model.UserSessionView, error) {
|
||||
return view.UserSessionsByAgentID(v.client, agentID, instanceID)
|
||||
func (v *View) UserSessionsByAgentID(ctx context.Context, agentID, instanceID string) ([]*model.UserSessionView, error) {
|
||||
return view.UserSessionsByAgentID(ctx, v.client, agentID, instanceID)
|
||||
}
|
||||
|
||||
func (v *View) UserAgentIDBySessionID(ctx context.Context, sessionID, instanceID string) (string, error) {
|
||||
return view.UserAgentIDBySessionID(ctx, v.client, sessionID, instanceID)
|
||||
}
|
||||
|
||||
func (v *View) ActiveUserIDsBySessionID(ctx context.Context, sessionID, instanceID string) (userAgentID string, userIDs []string, err error) {
|
||||
return view.ActiveUserIDsBySessionID(ctx, v.client, sessionID, instanceID)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestUserSessionSequence(ctx context.Context, instanceID string) (_ *query.CurrentState, err error) {
|
||||
|
@ -6,4 +6,6 @@ import (
|
||||
|
||||
type UserRepository interface {
|
||||
UserSessionUserIDsByAgentID(ctx context.Context, agentID string) ([]string, error)
|
||||
UserAgentIDBySessionID(ctx context.Context, sessionID string) (string, error)
|
||||
ActiveUserIDsBySessionID(ctx context.Context, sessionID string) (userAgentID string, userIDs []string, err error)
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ func (c *Commands) ApproveDeviceAuth(
|
||||
authTime time.Time,
|
||||
preferredLanguage *language.Tag,
|
||||
userAgent *domain.UserAgent,
|
||||
sessionID string,
|
||||
) (*domain.ObjectDetails, error) {
|
||||
model, err := c.getDeviceAuthWriteModelByDeviceCode(ctx, deviceCode)
|
||||
if err != nil {
|
||||
@ -58,7 +59,7 @@ func (c *Commands) ApproveDeviceAuth(
|
||||
if !model.State.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-Hief9", "Errors.DeviceAuth.NotFound")
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, deviceauth.NewApprovedEvent(ctx, model.aggregate, userID, userOrgID, authMethods, authTime, preferredLanguage, userAgent))
|
||||
pushedEvents, err := c.eventstore.Push(ctx, deviceauth.NewApprovedEvent(ctx, model.aggregate, userID, userOrgID, authMethods, authTime, preferredLanguage, userAgent, sessionID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -151,7 +152,7 @@ func (c *Commands) CreateOIDCSessionFromDeviceAuth(ctx context.Context, deviceCo
|
||||
cmd.AddSession(ctx,
|
||||
deviceAuthModel.UserID,
|
||||
deviceAuthModel.UserOrgID,
|
||||
"",
|
||||
deviceAuthModel.SessionID,
|
||||
deviceAuthModel.ClientID,
|
||||
deviceAuthModel.Audience,
|
||||
deviceAuthModel.Scopes,
|
||||
|
@ -28,6 +28,7 @@ type DeviceAuthWriteModel struct {
|
||||
PreferredLanguage *language.Tag
|
||||
UserAgent *domain.UserAgent
|
||||
NeedRefreshToken bool
|
||||
SessionID string
|
||||
}
|
||||
|
||||
func NewDeviceAuthWriteModel(deviceCode, resourceOwner string) *DeviceAuthWriteModel {
|
||||
@ -60,6 +61,7 @@ func (m *DeviceAuthWriteModel) Reduce() error {
|
||||
m.AuthTime = e.AuthTime
|
||||
m.PreferredLanguage = e.PreferredLanguage
|
||||
m.UserAgent = e.UserAgent
|
||||
m.SessionID = e.SessionID
|
||||
case *deviceauth.CanceledEvent:
|
||||
m.State = e.Reason.State()
|
||||
case *deviceauth.DoneEvent:
|
||||
|
@ -137,6 +137,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
authTime time.Time
|
||||
preferredLanguage *language.Tag
|
||||
userAgent *domain.UserAgent
|
||||
sessionID string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -161,6 +162,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
"sessionID",
|
||||
},
|
||||
wantErr: zerrors.ThrowNotFound(nil, "COMMAND-Hief9", "Errors.DeviceAuth.NotFound"),
|
||||
},
|
||||
@ -188,6 +190,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
"sessionID",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -201,6 +204,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
"sessionID",
|
||||
},
|
||||
wantErr: pushErr,
|
||||
},
|
||||
@ -228,6 +232,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
"sessionID",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -241,6 +246,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
"sessionID",
|
||||
},
|
||||
wantDetails: &domain.ObjectDetails{
|
||||
ResourceOwner: "instance1",
|
||||
@ -252,7 +258,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
gotDetails, err := c.ApproveDeviceAuth(tt.args.ctx, tt.args.id, tt.args.userID, tt.args.userOrgID, tt.args.authMethods, tt.args.authTime, tt.args.preferredLanguage, tt.args.userAgent)
|
||||
gotDetails, err := c.ApproveDeviceAuth(tt.args.ctx, tt.args.id, tt.args.userID, tt.args.userOrgID, tt.args.authMethods, tt.args.authTime, tt.args.preferredLanguage, tt.args.userAgent, tt.args.sessionID)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
assertObjectDetails(t, tt.wantDetails, gotDetails)
|
||||
})
|
||||
@ -607,13 +613,14 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
"sessionID",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"userID", "org1", "", "clientID", []string{"audience"}, []string{"openid", "offline_access"},
|
||||
"userID", "org1", "sessionID", "clientID", []string{"audience"}, []string{"openid", "offline_access"},
|
||||
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow, "", &language.Afrikaans, &domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
@ -657,7 +664,8 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
Reason: domain.TokenReasonAuthRequest,
|
||||
Reason: domain.TokenReasonAuthRequest,
|
||||
SessionID: "sessionID",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -687,13 +695,14 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
"sessionID",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"userID", "org1", "", "clientID", []string{"audience"}, []string{"openid", "offline_access"},
|
||||
"userID", "org1", "sessionID", "clientID", []string{"audience"}, []string{"openid", "offline_access"},
|
||||
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow, "", &language.Afrikaans, &domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
@ -742,6 +751,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
||||
},
|
||||
Reason: domain.TokenReasonAuthRequest,
|
||||
RefreshToken: "VjJfb2lkY1Nlc3Npb25JRC1ydF9yZWZyZXNoVG9rZW5JRDp1c2VySUQ", //V2_oidcSessionID-rt_refreshTokenID:userID
|
||||
SessionID: "sessionID",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ type InstanceFeatures struct {
|
||||
ImprovedPerformance []feature.ImprovedPerformanceType
|
||||
WebKey *bool
|
||||
DebugOIDCParentError *bool
|
||||
OIDCSingleV1SessionTermination *bool
|
||||
}
|
||||
|
||||
func (m *InstanceFeatures) isEmpty() bool {
|
||||
@ -37,7 +38,8 @@ func (m *InstanceFeatures) isEmpty() bool {
|
||||
// nil check to allow unset improvements
|
||||
m.ImprovedPerformance == nil &&
|
||||
m.WebKey == nil &&
|
||||
m.DebugOIDCParentError == nil
|
||||
m.DebugOIDCParentError == nil &&
|
||||
m.OIDCSingleV1SessionTermination == nil
|
||||
}
|
||||
|
||||
func (c *Commands) SetInstanceFeatures(ctx context.Context, f *InstanceFeatures) (*domain.ObjectDetails, error) {
|
||||
|
@ -69,6 +69,7 @@ func (m *InstanceFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v2.InstanceImprovedPerformanceEventType,
|
||||
feature_v2.InstanceWebKeyEventType,
|
||||
feature_v2.InstanceDebugOIDCParentErrorEventType,
|
||||
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType,
|
||||
).
|
||||
Builder().ResourceOwner(m.ResourceOwner)
|
||||
}
|
||||
@ -108,6 +109,9 @@ func reduceInstanceFeature(features *InstanceFeatures, key feature.Key, value an
|
||||
case feature.KeyDebugOIDCParentError:
|
||||
v := value.(bool)
|
||||
features.DebugOIDCParentError = &v
|
||||
case feature.KeyOIDCSingleV1SessionTermination:
|
||||
v := value.(bool)
|
||||
features.OIDCSingleV1SessionTermination = &v
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,5 +127,6 @@ func (wm *InstanceFeaturesWriteModel) setCommands(ctx context.Context, f *Instan
|
||||
cmds = appendFeatureSliceUpdate(ctx, cmds, aggregate, wm.ImprovedPerformance, f.ImprovedPerformance, feature_v2.InstanceImprovedPerformanceEventType)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.WebKey, f.WebKey, feature_v2.InstanceWebKeyEventType)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.DebugOIDCParentError, f.DebugOIDCParentError, feature_v2.InstanceDebugOIDCParentErrorEventType)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.OIDCSingleV1SessionTermination, f.OIDCSingleV1SessionTermination, feature_v2.InstanceOIDCSingleV1SessionTerminationEventType)
|
||||
return cmds
|
||||
}
|
||||
|
@ -208,6 +208,10 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceActionsEventType, true,
|
||||
),
|
||||
feature_v2.NewSetEvent[bool](
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType, true,
|
||||
),
|
||||
),
|
||||
),
|
||||
args: args{ctx, &InstanceFeatures{
|
||||
@ -216,6 +220,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
LegacyIntrospection: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(true),
|
||||
Actions: gu.Ptr(true),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||
}},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "instance1",
|
||||
@ -246,6 +251,10 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLegacyIntrospectionEventType, true,
|
||||
)),
|
||||
feature_v2.NewSetEvent[bool](
|
||||
context.Background(), aggregate,
|
||||
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType, false,
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
feature_v2.NewSetEvent[bool](
|
||||
@ -262,6 +271,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
TriggerIntrospectionProjections: gu.Ptr(false),
|
||||
LegacyIntrospection: gu.Ptr(true),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(false),
|
||||
}},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "instance1",
|
||||
|
@ -136,6 +136,7 @@ func (c *Commands) CreateOIDCSession(ctx context.Context,
|
||||
reason domain.TokenReason,
|
||||
actor *domain.TokenActor,
|
||||
needRefreshToken bool,
|
||||
sessionID string,
|
||||
) (session *OIDCSession, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
@ -151,7 +152,7 @@ func (c *Commands) CreateOIDCSession(ctx context.Context,
|
||||
cmd.UserImpersonated(ctx, userID, resourceOwner, clientID, actor)
|
||||
}
|
||||
|
||||
cmd.AddSession(ctx, userID, resourceOwner, "", clientID, audience, scope, authMethods, authTime, nonce, preferredLanguage, userAgent)
|
||||
cmd.AddSession(ctx, userID, resourceOwner, sessionID, clientID, audience, scope, authMethods, authTime, nonce, preferredLanguage, userAgent)
|
||||
if err = cmd.AddAccessToken(ctx, scope, userID, resourceOwner, reason, actor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -479,6 +479,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
reason domain.TokenReason
|
||||
actor *domain.TokenActor
|
||||
needRefreshToken bool
|
||||
sessionID string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -684,6 +685,89 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
RefreshToken: "VjJfb2lkY1Nlc3Npb25JRC1ydF9yZWZyZXNoVG9rZW5JRDp1c2VySUQ", //V2_oidcSessionID-rt_refreshTokenID:userID
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with sessionID",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"userID", "org1", "sessionID", "clientID", []string{"audience"}, []string{"openid", "offline_access"},
|
||||
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow, "nonce", &language.Afrikaans,
|
||||
&domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
),
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(),
|
||||
&oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"at_accessTokenID", []string{"openid", "offline_access"}, time.Hour, domain.TokenReasonAuthRequest,
|
||||
&domain.TokenActor{
|
||||
UserID: "user2",
|
||||
Issuer: "foo.com",
|
||||
},
|
||||
),
|
||||
user.NewUserTokenV2AddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate, "at_accessTokenID"),
|
||||
),
|
||||
),
|
||||
idGenerator: mock.NewIDGeneratorExpectIDs(t, "oidcSessionID", "accessTokenID"),
|
||||
defaultAccessTokenLifetime: time.Hour,
|
||||
defaultRefreshTokenLifetime: 7 * 24 * time.Hour,
|
||||
defaultRefreshTokenIdleLifetime: 24 * time.Hour,
|
||||
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "userID",
|
||||
resourceOwner: "org1",
|
||||
clientID: "clientID",
|
||||
audience: []string{"audience"},
|
||||
scope: []string{"openid", "offline_access"},
|
||||
authMethods: []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||
authTime: testNow,
|
||||
nonce: "nonce",
|
||||
preferredLanguage: &language.Afrikaans,
|
||||
userAgent: &domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
reason: domain.TokenReasonAuthRequest,
|
||||
actor: &domain.TokenActor{
|
||||
UserID: "user2",
|
||||
Issuer: "foo.com",
|
||||
},
|
||||
needRefreshToken: false,
|
||||
sessionID: "sessionID",
|
||||
},
|
||||
want: &OIDCSession{
|
||||
TokenID: "V2_oidcSessionID-at_accessTokenID",
|
||||
ClientID: "clientID",
|
||||
UserID: "userID",
|
||||
Audience: []string{"audience"},
|
||||
Expiration: time.Time{}.Add(time.Hour),
|
||||
Scope: []string{"openid", "offline_access"},
|
||||
AuthMethods: []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||
AuthTime: testNow,
|
||||
Nonce: "nonce",
|
||||
PreferredLanguage: &language.Afrikaans,
|
||||
UserAgent: &domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
Reason: domain.TokenReasonAuthRequest,
|
||||
Actor: &domain.TokenActor{
|
||||
UserID: "user2",
|
||||
Issuer: "foo.com",
|
||||
},
|
||||
SessionID: "sessionID",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "impersonation not allowed",
|
||||
fields: fields{
|
||||
@ -839,6 +923,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
tt.args.reason,
|
||||
tt.args.actor,
|
||||
tt.args.needRefreshToken,
|
||||
tt.args.sessionID,
|
||||
)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
if got != nil {
|
||||
|
@ -17,6 +17,7 @@ type SystemFeatures struct {
|
||||
UserSchema *bool
|
||||
Actions *bool
|
||||
ImprovedPerformance []feature.ImprovedPerformanceType
|
||||
OIDCSingleV1SessionTermination *bool
|
||||
}
|
||||
|
||||
func (m *SystemFeatures) isEmpty() bool {
|
||||
@ -27,7 +28,8 @@ func (m *SystemFeatures) isEmpty() bool {
|
||||
m.TokenExchange == nil &&
|
||||
m.Actions == nil &&
|
||||
// nil check to allow unset improvements
|
||||
m.ImprovedPerformance == nil
|
||||
m.ImprovedPerformance == nil &&
|
||||
m.OIDCSingleV1SessionTermination == nil
|
||||
}
|
||||
|
||||
func (c *Commands) SetSystemFeatures(ctx context.Context, f *SystemFeatures) (*domain.ObjectDetails, error) {
|
||||
|
@ -60,6 +60,7 @@ func (m *SystemFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v2.SystemTokenExchangeEventType,
|
||||
feature_v2.SystemActionsEventType,
|
||||
feature_v2.SystemImprovedPerformanceEventType,
|
||||
feature_v2.SystemOIDCSingleV1SessionTerminationEventType,
|
||||
).
|
||||
Builder().ResourceOwner(m.ResourceOwner)
|
||||
}
|
||||
@ -92,6 +93,9 @@ func reduceSystemFeature(features *SystemFeatures, key feature.Key, value any) {
|
||||
features.Actions = &v
|
||||
case feature.KeyImprovedPerformance:
|
||||
features.ImprovedPerformance = value.([]feature.ImprovedPerformanceType)
|
||||
case feature.KeyOIDCSingleV1SessionTermination:
|
||||
v := value.(bool)
|
||||
features.OIDCSingleV1SessionTermination = &v
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,6 +109,7 @@ func (wm *SystemFeaturesWriteModel) setCommands(ctx context.Context, f *SystemFe
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.TokenExchange, f.TokenExchange, feature_v2.SystemTokenExchangeEventType)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.Actions, f.Actions, feature_v2.SystemActionsEventType)
|
||||
cmds = appendFeatureSliceUpdate(ctx, cmds, aggregate, wm.ImprovedPerformance, f.ImprovedPerformance, feature_v2.SystemImprovedPerformanceEventType)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.OIDCSingleV1SessionTermination, f.OIDCSingleV1SessionTermination, feature_v2.SystemOIDCSingleV1SessionTerminationEventType)
|
||||
return cmds
|
||||
}
|
||||
|
||||
|
@ -176,6 +176,10 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemActionsEventType, true,
|
||||
),
|
||||
feature_v2.NewSetEvent[bool](
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemOIDCSingleV1SessionTerminationEventType, true,
|
||||
),
|
||||
),
|
||||
),
|
||||
args: args{context.Background(), &SystemFeatures{
|
||||
@ -184,6 +188,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
LegacyIntrospection: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(true),
|
||||
Actions: gu.Ptr(true),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||
}},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "SYSTEM",
|
||||
@ -232,6 +237,10 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemActionsEventType, false,
|
||||
),
|
||||
feature_v2.NewSetEvent[bool](
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemOIDCSingleV1SessionTerminationEventType, false,
|
||||
),
|
||||
),
|
||||
),
|
||||
args: args{context.Background(), &SystemFeatures{
|
||||
@ -240,6 +249,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
LegacyIntrospection: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(true),
|
||||
Actions: gu.Ptr(false),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(false),
|
||||
}},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "SYSTEM",
|
||||
|
@ -62,6 +62,8 @@ type AuthRequest struct {
|
||||
SAMLRequestID string
|
||||
// orgID the policies were last loaded with
|
||||
policyOrgID string
|
||||
// SessionID is set to the computed sessionID of the login session table
|
||||
SessionID string
|
||||
}
|
||||
|
||||
func (a *AuthRequest) SetPolicyOrgID(id string) {
|
||||
|
@ -146,18 +146,17 @@ func NewUpsertStatement(event eventstore.Event, conflictCols []Column, values []
|
||||
conflictTarget[i] = col.Name
|
||||
}
|
||||
|
||||
config := execConfig{
|
||||
args: args,
|
||||
}
|
||||
config := execConfig{}
|
||||
|
||||
if len(values) == 0 {
|
||||
config.err = ErrNoValues
|
||||
}
|
||||
|
||||
updateCols, updateVals := getUpdateCols(values, conflictTarget)
|
||||
updateCols, updateVals, args := getUpdateCols(values, conflictTarget, params, args)
|
||||
if len(updateCols) == 0 || len(updateVals) == 0 {
|
||||
config.err = ErrNoValues
|
||||
}
|
||||
config.args = args
|
||||
|
||||
q := func(config execConfig) string {
|
||||
var updateStmt string
|
||||
@ -194,18 +193,78 @@ func OnlySetValueOnInsert(table string, value interface{}) *onlySetValueOnInsert
|
||||
}
|
||||
}
|
||||
|
||||
func getUpdateCols(cols []Column, conflictTarget []string) (updateCols, updateVals []string) {
|
||||
type onlySetValueInCase struct {
|
||||
Table string
|
||||
Value interface{}
|
||||
Condition Condition
|
||||
}
|
||||
|
||||
func (c *onlySetValueInCase) GetValue() interface{} {
|
||||
return c.Value
|
||||
}
|
||||
|
||||
// ColumnChangedCondition checks the current value and if it changed to a specific new value
|
||||
func ColumnChangedCondition(table, column string, currentValue, newValue interface{}) Condition {
|
||||
return func(param string) (string, []any) {
|
||||
index, _ := strconv.Atoi(param)
|
||||
return fmt.Sprintf("%[1]s.%[2]s = $%[3]d AND EXCLUDED.%[2]s = $%[4]d", table, column, index, index+1), []any{currentValue, newValue}
|
||||
}
|
||||
}
|
||||
|
||||
// ColumnIsNullCondition checks if the current value is null
|
||||
func ColumnIsNullCondition(table, column string) Condition {
|
||||
return func(param string) (string, []any) {
|
||||
return fmt.Sprintf("%[1]s.%[2]s IS NULL", table, column), nil
|
||||
}
|
||||
}
|
||||
|
||||
// ConditionOr links multiple Conditions by OR
|
||||
func ConditionOr(conditions ...Condition) Condition {
|
||||
return func(param string) (_ string, args []any) {
|
||||
if len(conditions) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
b := strings.Builder{}
|
||||
s, arg := conditions[0](param)
|
||||
b.WriteString(s)
|
||||
args = append(args, arg...)
|
||||
for i := 1; i < len(conditions); i++ {
|
||||
b.WriteString(" OR ")
|
||||
s, condArgs := conditions[i](param)
|
||||
b.WriteString(s)
|
||||
args = append(args, condArgs...)
|
||||
}
|
||||
return b.String(), args
|
||||
}
|
||||
}
|
||||
|
||||
// OnlySetValueInCase will only update to the desired value if the condition applies
|
||||
func OnlySetValueInCase(table string, value interface{}, condition Condition) *onlySetValueInCase {
|
||||
return &onlySetValueInCase{
|
||||
Table: table,
|
||||
Value: value,
|
||||
Condition: condition,
|
||||
}
|
||||
}
|
||||
|
||||
func getUpdateCols(cols []Column, conflictTarget, params []string, args []interface{}) (updateCols, updateVals []string, updatedArgs []interface{}) {
|
||||
updateCols = make([]string, len(cols))
|
||||
updateVals = make([]string, len(cols))
|
||||
updatedArgs = args
|
||||
|
||||
for i := len(cols) - 1; i >= 0; i-- {
|
||||
col := cols[i]
|
||||
table := "EXCLUDED"
|
||||
if onlyOnInsert, ok := col.Value.(*onlySetValueOnInsert); ok {
|
||||
table = onlyOnInsert.Table
|
||||
}
|
||||
updateCols[i] = col.Name
|
||||
updateVals[i] = table + "." + col.Name
|
||||
switch v := col.Value.(type) {
|
||||
case *onlySetValueOnInsert:
|
||||
updateVals[i] = v.Table + "." + col.Name
|
||||
case *onlySetValueInCase:
|
||||
s, condArgs := v.Condition(strconv.Itoa(len(params) + 1))
|
||||
updatedArgs = append(updatedArgs, condArgs...)
|
||||
updateVals[i] = fmt.Sprintf("CASE WHEN %[1]s THEN EXCLUDED.%[2]s ELSE %[3]s.%[2]s END", s, col.Name, v.Table)
|
||||
default:
|
||||
updateVals[i] = "EXCLUDED" + "." + col.Name
|
||||
}
|
||||
for _, conflict := range conflictTarget {
|
||||
if conflict == col.Name {
|
||||
copy(updateCols[i:], updateCols[i+1:])
|
||||
@ -221,7 +280,7 @@ func getUpdateCols(cols []Column, conflictTarget []string) (updateCols, updateVa
|
||||
}
|
||||
}
|
||||
|
||||
return updateCols, updateVals
|
||||
return updateCols, updateVals, updatedArgs
|
||||
}
|
||||
|
||||
func NewUpdateStatement(event eventstore.Event, values []Column, conditions []Condition, opts ...execOption) *Statement {
|
||||
|
@ -451,6 +451,55 @@ func TestNewUpsertStatement(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct all *onlySetValueInCase",
|
||||
args: args{
|
||||
table: "my_table",
|
||||
event: &testEvent{
|
||||
aggregateType: "agg",
|
||||
sequence: 1,
|
||||
previousSequence: 0,
|
||||
},
|
||||
conflictCols: []Column{
|
||||
NewCol("col1", nil),
|
||||
},
|
||||
values: []Column{
|
||||
{
|
||||
Name: "col1",
|
||||
Value: "val1",
|
||||
},
|
||||
{
|
||||
Name: "col2",
|
||||
Value: &onlySetValueInCase{
|
||||
Table: "some.table",
|
||||
Value: "val2",
|
||||
Condition: ConditionOr(
|
||||
ColumnChangedCondition("some.table", "val3", 0, 1),
|
||||
ColumnIsNullCondition("some.table", "val3"),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
table: "my_table",
|
||||
aggregateType: "agg",
|
||||
sequence: 1,
|
||||
previousSequence: 1,
|
||||
executer: &wantExecuter{
|
||||
params: []params{
|
||||
{
|
||||
query: "INSERT INTO my_table (col1, col2) VALUES ($1, $2) ON CONFLICT (col1) DO UPDATE SET col2 = CASE WHEN some.table.val3 = $3 AND EXCLUDED.val3 = $4 OR some.table.val3 IS NULL THEN EXCLUDED.col2 ELSE some.table.col2 END",
|
||||
args: []interface{}{"val1", "val2", 0, 1},
|
||||
},
|
||||
},
|
||||
shouldExecute: true,
|
||||
},
|
||||
isErr: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -16,6 +16,7 @@ const (
|
||||
KeyImprovedPerformance
|
||||
KeyWebKey
|
||||
KeyDebugOIDCParentError
|
||||
KeyOIDCSingleV1SessionTermination
|
||||
)
|
||||
|
||||
//go:generate enumer -type Level -transform snake -trimprefix Level
|
||||
@ -41,6 +42,7 @@ type Features struct {
|
||||
ImprovedPerformance []ImprovedPerformanceType `json:"improved_performance,omitempty"`
|
||||
WebKey bool `json:"web_key,omitempty"`
|
||||
DebugOIDCParentError bool `json:"debug_oidc_parent_error,omitempty"`
|
||||
OIDCSingleV1SessionTermination bool `json:"terminate_single_v1_session,omitempty"`
|
||||
}
|
||||
|
||||
type ImprovedPerformanceType int32
|
||||
|
@ -7,11 +7,11 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const _KeyName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspectionuser_schematoken_exchangeactionsimproved_performanceweb_keydebug_oidc_parent_error"
|
||||
const _KeyName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspectionuser_schematoken_exchangeactionsimproved_performanceweb_keydebug_oidc_parent_errorterminate_single_v1_session"
|
||||
|
||||
var _KeyIndex = [...]uint8{0, 11, 28, 61, 81, 92, 106, 113, 133, 140, 163}
|
||||
var _KeyIndex = [...]uint8{0, 11, 28, 61, 81, 92, 106, 113, 133, 140, 163, 190}
|
||||
|
||||
const _KeyLowerName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspectionuser_schematoken_exchangeactionsimproved_performanceweb_keydebug_oidc_parent_error"
|
||||
const _KeyLowerName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspectionuser_schematoken_exchangeactionsimproved_performanceweb_keydebug_oidc_parent_errorterminate_single_v1_session"
|
||||
|
||||
func (i Key) String() string {
|
||||
if i < 0 || i >= Key(len(_KeyIndex)-1) {
|
||||
@ -34,9 +34,10 @@ func _KeyNoOp() {
|
||||
_ = x[KeyImprovedPerformance-(7)]
|
||||
_ = x[KeyWebKey-(8)]
|
||||
_ = x[KeyDebugOIDCParentError-(9)]
|
||||
_ = x[KeyOIDCSingleV1SessionTermination-(10)]
|
||||
}
|
||||
|
||||
var _KeyValues = []Key{KeyUnspecified, KeyLoginDefaultOrg, KeyTriggerIntrospectionProjections, KeyLegacyIntrospection, KeyUserSchema, KeyTokenExchange, KeyActions, KeyImprovedPerformance, KeyWebKey, KeyDebugOIDCParentError}
|
||||
var _KeyValues = []Key{KeyUnspecified, KeyLoginDefaultOrg, KeyTriggerIntrospectionProjections, KeyLegacyIntrospection, KeyUserSchema, KeyTokenExchange, KeyActions, KeyImprovedPerformance, KeyWebKey, KeyDebugOIDCParentError, KeyOIDCSingleV1SessionTermination}
|
||||
|
||||
var _KeyNameToValueMap = map[string]Key{
|
||||
_KeyName[0:11]: KeyUnspecified,
|
||||
@ -59,6 +60,8 @@ var _KeyNameToValueMap = map[string]Key{
|
||||
_KeyLowerName[133:140]: KeyWebKey,
|
||||
_KeyName[140:163]: KeyDebugOIDCParentError,
|
||||
_KeyLowerName[140:163]: KeyDebugOIDCParentError,
|
||||
_KeyName[163:190]: KeyOIDCSingleV1SessionTermination,
|
||||
_KeyLowerName[163:190]: KeyOIDCSingleV1SessionTermination,
|
||||
}
|
||||
|
||||
var _KeyNames = []string{
|
||||
@ -72,6 +75,7 @@ var _KeyNames = []string{
|
||||
_KeyName[113:133],
|
||||
_KeyName[133:140],
|
||||
_KeyName[140:163],
|
||||
_KeyName[163:190],
|
||||
}
|
||||
|
||||
// KeyString retrieves an enum value from the enum constants string name.
|
||||
|
61
internal/notification/static/i18n/id.yaml
Normal file
61
internal/notification/static/i18n/id.yaml
Normal file
@ -0,0 +1,61 @@
|
||||
InitCode:
|
||||
Title: Inisialisasi Pengguna
|
||||
PreHeader: Inisialisasi Pengguna
|
||||
Subject: Inisialisasi Pengguna
|
||||
Greeting: 'Halo {{.DisplayName}},'
|
||||
Text: Pengguna ini telah dibuat. {{.PreferredLoginName}} untuk masuk. {{.Code}}) Jika Anda tidak meminta email ini, abaikan saja.
|
||||
ButtonText: Selesaikan inisialisasi
|
||||
PasswordReset:
|
||||
Title: Setel ulang kata sandi
|
||||
PreHeader: Setel ulang kata sandi
|
||||
Subject: Setel ulang kata sandi
|
||||
Greeting: 'Halo {{.DisplayName}},'
|
||||
Text: Kami menerima permintaan pengaturan ulang kata sandi. {{.Code}}) Jika Anda tidak meminta email ini, abaikan saja.
|
||||
ButtonText: Setel ulang kata sandi
|
||||
VerifyEmail:
|
||||
Title: Verifikasi email
|
||||
PreHeader: Verifikasi email
|
||||
Subject: Verifikasi email
|
||||
Greeting: 'Halo {{.DisplayName}},'
|
||||
Text: Email baru telah ditambahkan. {{.Code}}) Jika Anda tidak menambahkan email baru, harap abaikan email ini.
|
||||
ButtonText: Verifikasi email
|
||||
VerifyPhone:
|
||||
Title: Verifikasi telepon
|
||||
PreHeader: Verifikasi telepon
|
||||
Subject: Verifikasi telepon
|
||||
Greeting: 'Halo {{.DisplayName}},'
|
||||
Text: 'Nomor telepon baru telah ditambahkan. {{.Code}}'
|
||||
ButtonText: Verifikasi telepon
|
||||
VerifyEmailOTP:
|
||||
Title: Verifikasi Kata Sandi Sekali Pakai
|
||||
PreHeader: Verifikasi Kata Sandi Sekali Pakai
|
||||
Subject: Verifikasi Kata Sandi Sekali Pakai
|
||||
Greeting: 'Halo {{.DisplayName}},'
|
||||
Text: Silakan gunakan kata sandi satu kali {{.OTP}} untuk mengautentikasi dalam lima menit berikutnya atau klik tombol "Otentikasi".
|
||||
ButtonText: Otentikasi
|
||||
VerifySMSOTP:
|
||||
Text: >-
|
||||
{{.OTP}} adalah kata sandi satu kali Anda untuk {{ .Domain }}. {{.Expiry}}.
|
||||
|
||||
{{.Domain}} #{{.OTP}}
|
||||
DomainClaimed:
|
||||
Title: Domain telah diklaim
|
||||
PreHeader: Ganti email/nama pengguna
|
||||
Subject: Domain telah diklaim
|
||||
Greeting: 'Halo {{.DisplayName}},'
|
||||
Text: Domainnya {{.Domain}} telah diklaim oleh suatu organisasi. {{.Username}} bukan bagian dari organisasi ini. {{.TempUsername}}) untuk login ini.
|
||||
ButtonText: Login
|
||||
PasswordlessRegistration:
|
||||
Title: Tambahkan Login Tanpa Kata Sandi
|
||||
PreHeader: Tambahkan Login Tanpa Kata Sandi
|
||||
Subject: Tambahkan Login Tanpa Kata Sandi
|
||||
Greeting: 'Halo {{.DisplayName}},'
|
||||
Text: Kami menerima permintaan untuk menambahkan token untuk login tanpa kata sandi.
|
||||
ButtonText: Tambahkan Login Tanpa Kata Sandi
|
||||
PasswordChange:
|
||||
Title: Kata sandi pengguna telah berubah
|
||||
PreHeader: Ubah kata sandi
|
||||
Subject: Kata sandi pengguna telah berubah
|
||||
Greeting: 'Halo {{.DisplayName}},'
|
||||
Text: 'Kata sandi pengguna Anda telah berubah. '
|
||||
ButtonText: Login
|
@ -18,6 +18,7 @@ type InstanceFeatures struct {
|
||||
ImprovedPerformance FeatureSource[[]feature.ImprovedPerformanceType]
|
||||
WebKey FeatureSource[bool]
|
||||
DebugOIDCParentError FeatureSource[bool]
|
||||
OIDCSingleV1SessionTermination FeatureSource[bool]
|
||||
}
|
||||
|
||||
func (q *Queries) GetInstanceFeatures(ctx context.Context, cascade bool) (_ *InstanceFeatures, err error) {
|
||||
|
@ -69,6 +69,7 @@ func (m *InstanceFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v2.InstanceImprovedPerformanceEventType,
|
||||
feature_v2.InstanceWebKeyEventType,
|
||||
feature_v2.InstanceDebugOIDCParentErrorEventType,
|
||||
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType,
|
||||
).
|
||||
Builder().ResourceOwner(m.ResourceOwner)
|
||||
}
|
||||
@ -92,6 +93,7 @@ func (m *InstanceFeaturesReadModel) populateFromSystem() bool {
|
||||
m.instance.TokenExchange = m.system.TokenExchange
|
||||
m.instance.Actions = m.system.Actions
|
||||
m.instance.ImprovedPerformance = m.system.ImprovedPerformance
|
||||
m.instance.OIDCSingleV1SessionTermination = m.system.OIDCSingleV1SessionTermination
|
||||
return true
|
||||
}
|
||||
|
||||
@ -121,6 +123,8 @@ func reduceInstanceFeatureSet[T any](features *InstanceFeatures, event *feature_
|
||||
features.WebKey.set(level, event.Value)
|
||||
case feature.KeyDebugOIDCParentError:
|
||||
features.DebugOIDCParentError.set(level, event.Value)
|
||||
case feature.KeyOIDCSingleV1SessionTermination:
|
||||
features.OIDCSingleV1SessionTermination.set(level, event.Value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -96,6 +96,10 @@ func (*instanceFeatureProjection) Reducers() []handler.AggregateReducer {
|
||||
Event: feature_v2.InstanceDebugOIDCParentErrorEventType,
|
||||
Reduce: reduceInstanceSetFeature[bool],
|
||||
},
|
||||
{
|
||||
Event: feature_v2.InstanceOIDCSingleV1SessionTerminationEventType,
|
||||
Reduce: reduceInstanceSetFeature[bool],
|
||||
},
|
||||
{
|
||||
Event: instance.InstanceRemovedEventType,
|
||||
Reduce: reduceInstanceRemovedHelper(InstanceDomainInstanceIDCol),
|
||||
|
@ -27,6 +27,7 @@ type SystemFeatures struct {
|
||||
TokenExchange FeatureSource[bool]
|
||||
Actions FeatureSource[bool]
|
||||
ImprovedPerformance FeatureSource[[]feature.ImprovedPerformanceType]
|
||||
OIDCSingleV1SessionTermination FeatureSource[bool]
|
||||
}
|
||||
|
||||
func (q *Queries) GetSystemFeatures(ctx context.Context) (_ *SystemFeatures, err error) {
|
||||
|
@ -57,6 +57,7 @@ func (m *SystemFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v2.SystemTokenExchangeEventType,
|
||||
feature_v2.SystemActionsEventType,
|
||||
feature_v2.SystemImprovedPerformanceEventType,
|
||||
feature_v2.SystemOIDCSingleV1SessionTerminationEventType,
|
||||
).
|
||||
Builder().ResourceOwner(m.ResourceOwner)
|
||||
}
|
||||
@ -88,6 +89,8 @@ func reduceSystemFeatureSet[T any](features *SystemFeatures, event *feature_v2.S
|
||||
features.Actions.set(level, event.Value)
|
||||
case feature.KeyImprovedPerformance:
|
||||
features.ImprovedPerformance.set(level, event.Value)
|
||||
case feature.KeyOIDCSingleV1SessionTermination:
|
||||
features.OIDCSingleV1SessionTermination.set(level, event.Value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -72,6 +72,7 @@ type ApprovedEvent struct {
|
||||
AuthTime time.Time
|
||||
PreferredLanguage *language.Tag
|
||||
UserAgent *domain.UserAgent
|
||||
SessionID string
|
||||
}
|
||||
|
||||
func (e *ApprovedEvent) SetBaseEvent(b *eventstore.BaseEvent) {
|
||||
@ -95,17 +96,19 @@ func NewApprovedEvent(
|
||||
authTime time.Time,
|
||||
preferredLanguage *language.Tag,
|
||||
userAgent *domain.UserAgent,
|
||||
sessionID string,
|
||||
) *ApprovedEvent {
|
||||
return &ApprovedEvent{
|
||||
eventstore.NewBaseEventForPush(
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx, aggregate, ApprovedEventType,
|
||||
),
|
||||
userID,
|
||||
userOrgID,
|
||||
userAuthMethods,
|
||||
authTime,
|
||||
preferredLanguage,
|
||||
userAgent,
|
||||
UserID: userID,
|
||||
UserOrgID: userOrgID,
|
||||
UserAuthMethods: userAuthMethods,
|
||||
AuthTime: authTime,
|
||||
PreferredLanguage: preferredLanguage,
|
||||
UserAgent: userAgent,
|
||||
SessionID: sessionID,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ func init() {
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SystemTokenExchangeEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SystemActionsEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SystemImprovedPerformanceEventType, eventstore.GenericEventMapper[SetEvent[[]feature.ImprovedPerformanceType]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceOIDCSingleV1SessionTerminationEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceResetEventType, eventstore.GenericEventMapper[ResetEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceLoginDefaultOrgEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
@ -25,4 +26,5 @@ func init() {
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceImprovedPerformanceEventType, eventstore.GenericEventMapper[SetEvent[[]feature.ImprovedPerformanceType]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceWebKeyEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceDebugOIDCParentErrorEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceOIDCSingleV1SessionTerminationEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ var (
|
||||
SystemTokenExchangeEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyTokenExchange)
|
||||
SystemActionsEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyActions)
|
||||
SystemImprovedPerformanceEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyImprovedPerformance)
|
||||
SystemOIDCSingleV1SessionTerminationEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyOIDCSingleV1SessionTermination)
|
||||
|
||||
InstanceResetEventType = resetEventTypeFromFeature(feature.LevelInstance)
|
||||
InstanceLoginDefaultOrgEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyLoginDefaultOrg)
|
||||
@ -30,6 +31,7 @@ var (
|
||||
InstanceImprovedPerformanceEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyImprovedPerformance)
|
||||
InstanceWebKeyEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyWebKey)
|
||||
InstanceDebugOIDCParentErrorEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyDebugOIDCParentError)
|
||||
InstanceOIDCSingleV1SessionTerminationEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyOIDCSingleV1SessionTermination)
|
||||
)
|
||||
|
||||
const (
|
||||
|
1365
internal/static/i18n/id.yaml
Normal file
1365
internal/static/i18n/id.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@ -27,6 +27,7 @@ type UserSessionView struct {
|
||||
MultiFactorVerification time.Time
|
||||
MultiFactorVerificationType domain.MFAType
|
||||
Sequence uint64
|
||||
ID string
|
||||
}
|
||||
|
||||
type UserSessionSearchRequest struct {
|
||||
|
@ -0,0 +1,11 @@
|
||||
SELECT
|
||||
s.user_agent_id,
|
||||
s.user_id
|
||||
FROM auth.user_sessions s
|
||||
JOIN auth.user_sessions s2
|
||||
ON s.instance_id = s2.instance_id
|
||||
AND s.user_agent_id = s2.user_agent_id
|
||||
WHERE
|
||||
s2.id = $1
|
||||
AND s.instance_id = $2
|
||||
AND s.state = 0;
|
@ -32,6 +32,7 @@ const (
|
||||
UserSessionKeyPasswordlessVerification = "passwordless_verification"
|
||||
UserSessionKeyExternalLoginVerification = "external_login_verification"
|
||||
UserSessionKeySelectedIDPConfigID = "selected_idp_config_id"
|
||||
UserSessionKeyID = "id"
|
||||
)
|
||||
|
||||
type UserSessionView struct {
|
||||
@ -59,6 +60,12 @@ type UserSessionView struct {
|
||||
MultiFactorVerificationType sql.NullInt32 `json:"-" gorm:"column:multi_factor_verification_type"`
|
||||
Sequence uint64 `json:"-" gorm:"column:sequence"`
|
||||
InstanceID string `json:"instanceID" gorm:"column:instance_id;primary_key"`
|
||||
ID sql.NullString `json:"id" gorm:"-"`
|
||||
}
|
||||
|
||||
type ActiveUserAgentUserIDs struct {
|
||||
UserAgentID string
|
||||
UserIDs []string
|
||||
}
|
||||
|
||||
type userAgentIDPayload struct {
|
||||
@ -95,6 +102,7 @@ func UserSessionToModel(userSession *UserSessionView) *model.UserSessionView {
|
||||
MultiFactorVerification: userSession.MultiFactorVerification.Time,
|
||||
MultiFactorVerificationType: domain.MFAType(userSession.MultiFactorVerificationType.Int32),
|
||||
Sequence: userSession.Sequence,
|
||||
ID: userSession.ID.String,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,7 @@
|
||||
SELECT
|
||||
s.user_agent_id
|
||||
FROM auth.user_sessions s
|
||||
WHERE
|
||||
s.id = $1
|
||||
AND s.instance_id = $2
|
||||
LIMIT 1;
|
@ -17,7 +17,8 @@ SELECT s.creation_date,
|
||||
s.multi_factor_verification,
|
||||
s.multi_factor_verification_type,
|
||||
s.sequence,
|
||||
s.instance_id
|
||||
s.instance_id,
|
||||
s.id
|
||||
FROM auth.user_sessions s
|
||||
LEFT JOIN projections.users13 u ON s.user_id = u.id AND s.instance_id = u.instance_id
|
||||
LEFT JOIN projections.users13_humans h ON s.user_id = h.user_id AND s.instance_id = h.instance_id
|
||||
|
@ -1,6 +1,7 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"errors"
|
||||
@ -16,8 +17,15 @@ var userSessionByIDQuery string
|
||||
//go:embed user_sessions_by_user_agent.sql
|
||||
var userSessionsByUserAgentQuery string
|
||||
|
||||
func UserSessionByIDs(db *database.DB, agentID, userID, instanceID string) (userSession *model.UserSessionView, err error) {
|
||||
err = db.QueryRow(
|
||||
//go:embed user_agent_by_user_session_id.sql
|
||||
var userAgentByUserSessionIDQuery string
|
||||
|
||||
//go:embed active_user_ids_by_session_id.sql
|
||||
var activeUserIDsBySessionIDQuery string
|
||||
|
||||
func UserSessionByIDs(ctx context.Context, db *database.DB, agentID, userID, instanceID string) (userSession *model.UserSessionView, err error) {
|
||||
err = db.QueryRowContext(
|
||||
ctx,
|
||||
func(row *sql.Row) error {
|
||||
userSession, err = scanUserSession(row)
|
||||
return err
|
||||
@ -29,8 +37,10 @@ func UserSessionByIDs(db *database.DB, agentID, userID, instanceID string) (user
|
||||
)
|
||||
return userSession, err
|
||||
}
|
||||
func UserSessionsByAgentID(db *database.DB, agentID, instanceID string) (userSessions []*model.UserSessionView, err error) {
|
||||
err = db.Query(
|
||||
|
||||
func UserSessionsByAgentID(ctx context.Context, db *database.DB, agentID, instanceID string) (userSessions []*model.UserSessionView, err error) {
|
||||
err = db.QueryContext(
|
||||
ctx,
|
||||
func(rows *sql.Rows) error {
|
||||
userSessions, err = scanUserSessions(rows)
|
||||
return err
|
||||
@ -42,6 +52,51 @@ func UserSessionsByAgentID(db *database.DB, agentID, instanceID string) (userSes
|
||||
return userSessions, err
|
||||
}
|
||||
|
||||
func UserAgentIDBySessionID(ctx context.Context, db *database.DB, sessionID, instanceID string) (userAgentID string, err error) {
|
||||
err = db.QueryRowContext(
|
||||
ctx,
|
||||
func(row *sql.Row) error {
|
||||
return row.Scan(&userAgentID)
|
||||
},
|
||||
userAgentByUserSessionIDQuery,
|
||||
sessionID,
|
||||
instanceID,
|
||||
)
|
||||
return userAgentID, err
|
||||
}
|
||||
|
||||
// ActiveUserIDsBySessionID returns all userIDs with an active session on the same user agent (its id is also returned) based on a sessionID
|
||||
func ActiveUserIDsBySessionID(ctx context.Context, db *database.DB, sessionID, instanceID string) (userAgentID string, userIDs []string, err error) {
|
||||
err = db.QueryContext(
|
||||
ctx,
|
||||
func(rows *sql.Rows) error {
|
||||
userAgentID, userIDs, err = scanActiveUserAgentUserIDs(rows)
|
||||
return err
|
||||
},
|
||||
activeUserIDsBySessionIDQuery,
|
||||
sessionID,
|
||||
instanceID,
|
||||
)
|
||||
return userAgentID, userIDs, err
|
||||
}
|
||||
|
||||
func scanActiveUserAgentUserIDs(rows *sql.Rows) (userAgentID string, userIDs []string, err error) {
|
||||
for rows.Next() {
|
||||
var userID string
|
||||
err := rows.Scan(
|
||||
&userAgentID,
|
||||
&userID)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
userIDs = append(userIDs, userID)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return "", nil, zerrors.ThrowInternal(err, "VIEW-Sbrws", "Errors.Query.CloseRows")
|
||||
}
|
||||
return userAgentID, userIDs, nil
|
||||
}
|
||||
|
||||
func scanUserSession(row *sql.Row) (*model.UserSessionView, error) {
|
||||
session := new(model.UserSessionView)
|
||||
err := row.Scan(
|
||||
@ -65,6 +120,7 @@ func scanUserSession(row *sql.Row) (*model.UserSessionView, error) {
|
||||
&session.MultiFactorVerificationType,
|
||||
&session.Sequence,
|
||||
&session.InstanceID,
|
||||
&session.ID,
|
||||
)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "VIEW-NGBs1", "Errors.UserSession.NotFound")
|
||||
@ -97,6 +153,7 @@ func scanUserSessions(rows *sql.Rows) ([]*model.UserSessionView, error) {
|
||||
&session.MultiFactorVerificationType,
|
||||
&session.Sequence,
|
||||
&session.InstanceID,
|
||||
&session.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -17,7 +17,8 @@ SELECT s.creation_date,
|
||||
s.multi_factor_verification,
|
||||
s.multi_factor_verification_type,
|
||||
s.sequence,
|
||||
s.instance_id
|
||||
s.instance_id,
|
||||
s.id
|
||||
FROM auth.user_sessions s
|
||||
LEFT JOIN projections.users13 u ON s.user_id = u.id AND s.instance_id = u.instance_id
|
||||
LEFT JOIN projections.users13_humans h ON s.user_id = h.user_id AND s.instance_id = h.instance_id
|
||||
|
@ -72,6 +72,13 @@ message SetInstanceFeaturesRequest{
|
||||
description: "Return parent errors to OIDC clients for debugging purposes. Parent errors may contain sensitive data or unwanted details about the system status of zitadel. Only enable if really needed.";
|
||||
}
|
||||
];
|
||||
|
||||
optional bool oidc_single_v1_session_termination = 10 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "true";
|
||||
description: "If the flag is enabled, you'll be able to terminate a single session from the login UI by providing an id_token with a `sid` claim as id_token_hint on the end_session endpoint. Note that currently all sessions from the same user agent (browser) are terminated in the login UI. Sessions managed through the Session API already allow the termination of single sessions.";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message SetInstanceFeaturesResponse {
|
||||
@ -157,4 +164,11 @@ message GetInstanceFeaturesResponse {
|
||||
description: "Return parent errors to OIDC clients for debugging purposes. Parent errors may contain sensitive data or unwanted details about the system status of zitadel. Only enable if really needed.";
|
||||
}
|
||||
];
|
||||
|
||||
FeatureFlag oidc_single_v1_session_termination = 11 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "true";
|
||||
description: "If the flag is enabled, you'll be able to terminate a single session from the login UI by providing an id_token with a `sid` claim as id_token_hint on the end_session endpoint. Note that currently all sessions from the same user agent (browser) are terminated in the login UI. Sessions managed through the Session API already allow the termination of single sessions.";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -61,6 +61,13 @@ message SetSystemFeaturesRequest{
|
||||
description: "Improves performance of specified execution paths.";
|
||||
}
|
||||
];
|
||||
|
||||
optional bool oidc_single_v1_session_termination = 8 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "true";
|
||||
description: "If the flag is enabled, you'll be able to terminate a single session from the login UI by providing an id_token with a `sid` claim as id_token_hint on the end_session endpoint. Note that currently all sessions from the same user agent (browser) are terminated in the login UI. Sessions managed through the Session API already allow the termination of single sessions.";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message SetSystemFeaturesResponse {
|
||||
@ -125,4 +132,11 @@ message GetSystemFeaturesResponse {
|
||||
description: "Improves performance of specified execution paths.";
|
||||
}
|
||||
];
|
||||
|
||||
FeatureFlag oidc_single_v1_session_termination = 9 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "true";
|
||||
description: "If the flag is enabled, you'll be able to terminate a single session from the login UI by providing an id_token with a `sid` claim as id_token_hint on the end_session endpoint. Note that currently all sessions from the same user agent (browser) are terminated in the login UI. Sessions managed through the Session API already allow the termination of single sessions.";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -72,6 +72,13 @@ message SetInstanceFeaturesRequest{
|
||||
description: "Return parent errors to OIDC clients for debugging purposes. Parent errors may contain sensitive data or unwanted details about the system status of zitadel. Only enable if really needed.";
|
||||
}
|
||||
];
|
||||
|
||||
optional bool oidc_single_v1_session_termination = 10 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "true";
|
||||
description: "If the flag is enabled, you'll be able to terminate a single session from the login UI by providing an id_token with a `sid` claim as id_token_hint on the end_session endpoint. Note that currently all sessions from the same user agent (browser) are terminated in the login UI. Sessions managed through the Session API already allow the termination of single sessions.";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message SetInstanceFeaturesResponse {
|
||||
@ -157,4 +164,11 @@ message GetInstanceFeaturesResponse {
|
||||
description: "Return parent errors to OIDC clients for debugging purposes. Parent errors may contain sensitive data or unwanted details about the system status of zitadel. Only enable if really needed.";
|
||||
}
|
||||
];
|
||||
|
||||
FeatureFlag oidc_single_v1_session_termination = 11 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "true";
|
||||
description: "If the flag is enabled, you'll be able to terminate a single session from the login UI by providing an id_token with a `sid` claim as id_token_hint on the end_session endpoint. Note that currently all sessions from the same user agent (browser) are terminated in the login UI. Sessions managed through the Session API already allow the termination of single sessions.";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -61,6 +61,13 @@ message SetSystemFeaturesRequest{
|
||||
description: "Improves performance of specified execution paths.";
|
||||
}
|
||||
];
|
||||
|
||||
optional bool oidc_single_v1_session_termination = 8 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "true";
|
||||
description: "If the flag is enabled, you'll be able to terminate a single session from the login UI by providing an id_token with a `sid` claim as id_token_hint on the end_session endpoint. Note that currently all sessions from the same user agent (browser) are terminated in the login UI. Sessions managed through the Session API already allow the termination of single sessions.";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message SetSystemFeaturesResponse {
|
||||
@ -125,4 +132,11 @@ message GetSystemFeaturesResponse {
|
||||
description: "Improves performance of specified execution paths.";
|
||||
}
|
||||
];
|
||||
|
||||
FeatureFlag oidc_single_v1_session_termination = 9 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "true";
|
||||
description: "If the flag is enabled, you'll be able to terminate a single session from the login UI by providing an id_token with a `sid` claim as id_token_hint on the end_session endpoint. Note that currently all sessions from the same user agent (browser) are terminated in the login UI. Sessions managed through the Session API already allow the termination of single sessions.";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user