Merge branch 'main' into next

This commit is contained in:
Livio Spring 2024-09-04 12:50:47 +02:00
commit 937f683ce5
No known key found for this signature in database
GPG Key ID: 26BB1C2FA5952CF0
107 changed files with 5237 additions and 225 deletions

View File

@ -126,3 +126,23 @@ jobs:
GH_TOKEN: ${{ steps.generate-token.outputs.token }} GH_TOKEN: ${{ steps.generate-token.outputs.token }}
run: | run: |
gh workflow -R zitadel/homebrew-tap run update.yml -f runId=${RUN_ID} -f version=${VERSION} 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
View 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
View 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);

View File

@ -116,6 +116,7 @@ type Steps struct {
s29FillFieldsForProjectGrant *FillFieldsForProjectGrant s29FillFieldsForProjectGrant *FillFieldsForProjectGrant
s30FillFieldsForOrgDomainVerified *FillFieldsForOrgDomainVerified s30FillFieldsForOrgDomainVerified *FillFieldsForOrgDomainVerified
s31AddAggregateIndexToFields *AddAggregateIndexToFields s31AddAggregateIndexToFields *AddAggregateIndexToFields
s32AddAuthSessionID *AddAuthSessionID
} }
func MustNewSteps(v *viper.Viper) *Steps { func MustNewSteps(v *viper.Viper) *Steps {

View File

@ -160,6 +160,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
steps.s29FillFieldsForProjectGrant = &FillFieldsForProjectGrant{eventstore: eventstoreClient} steps.s29FillFieldsForProjectGrant = &FillFieldsForProjectGrant{eventstore: eventstoreClient}
steps.s30FillFieldsForOrgDomainVerified = &FillFieldsForOrgDomainVerified{eventstore: eventstoreClient} steps.s30FillFieldsForOrgDomainVerified = &FillFieldsForOrgDomainVerified{eventstore: eventstoreClient}
steps.s31AddAggregateIndexToFields = &AddAggregateIndexToFields{dbClient: esPusherDBClient} steps.s31AddAggregateIndexToFields = &AddAggregateIndexToFields{dbClient: esPusherDBClient}
steps.s32AddAuthSessionID = &AddAuthSessionID{dbClient: esPusherDBClient}
err = projection.Create(ctx, projectionDBClient, eventstoreClient, config.Projections, nil, nil, nil) err = projection.Create(ctx, projectionDBClient, eventstoreClient, config.Projections, nil, nil, nil)
logging.OnError(err).Fatal("unable to start projections") 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.s21AddBlockFieldToLimits,
steps.s25User11AddLowerFieldsToVerifiedEmail, steps.s25User11AddLowerFieldsToVerifiedEmail,
steps.s27IDPTemplate6SAMLNameIDFormat, steps.s27IDPTemplate6SAMLNameIDFormat,
steps.s32AddAuthSessionID,
} { } {
mustExecuteMigration(ctx, eventstoreClient, step, "migration failed") mustExecuteMigration(ctx, eventstoreClient, step, "migration failed")
} }

View File

@ -6,6 +6,7 @@ import localeCs from '@angular/common/locales/cs';
import localeEn from '@angular/common/locales/en'; import localeEn from '@angular/common/locales/en';
import localeEs from '@angular/common/locales/es'; import localeEs from '@angular/common/locales/es';
import localeFr from '@angular/common/locales/fr'; import localeFr from '@angular/common/locales/fr';
import localeId from '@angular/common/locales/id';
import localeIt from '@angular/common/locales/it'; import localeIt from '@angular/common/locales/it';
import localeJa from '@angular/common/locales/ja'; import localeJa from '@angular/common/locales/ja';
import localeMk from '@angular/common/locales/mk'; import localeMk from '@angular/common/locales/mk';
@ -80,6 +81,8 @@ registerLocaleData(localeEs);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/es.json')); i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/es.json'));
registerLocaleData(localeFr); registerLocaleData(localeFr);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/fr.json')); i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/fr.json'));
registerLocaleData(localeId);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/id.json'));
registerLocaleData(localeIt); registerLocaleData(localeIt);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/it.json')); i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/it.json'));
registerLocaleData(localeJa); registerLocaleData(localeJa);

View File

@ -348,6 +348,61 @@
</div> </div>
<cnsl-info-section class="feature-info">{{ 'SETTING.FEATURES.ACTIONS_DESCRIPTION' | translate }}</cnsl-info-section> <cnsl-info-section class="feature-info">{{ 'SETTING.FEATURES.ACTIONS_DESCRIPTION' | translate }}</cnsl-info-section>
</div> </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> </div>
</cnsl-card> </cnsl-card>
</div> </div>

View File

@ -38,6 +38,7 @@ type ToggleStates = {
userSchema?: FeatureState; userSchema?: FeatureState;
oidcTokenExchange?: FeatureState; oidcTokenExchange?: FeatureState;
actions?: FeatureState; actions?: FeatureState;
oidcSingleV1SessionTermination?: FeatureState;
}; };
@Component({ @Component({
@ -135,6 +136,12 @@ export class FeaturesComponent implements OnDestroy {
req.setActions(this.toggleStates?.actions?.state === ToggleState.ENABLED); req.setActions(this.toggleStates?.actions?.state === ToggleState.ENABLED);
changed = true; changed = true;
} }
if (this.toggleStates?.oidcSingleV1SessionTermination?.state !== ToggleState.INHERITED) {
req.setOidcSingleV1SessionTermination(
this.toggleStates?.oidcSingleV1SessionTermination?.state === ToggleState.ENABLED,
);
changed = true;
}
if (changed) { if (changed) {
this.featureService this.featureService
@ -215,6 +222,16 @@ export class FeaturesComponent implements OnDestroy {
? ToggleState.ENABLED ? ToggleState.ENABLED
: ToggleState.DISABLED, : 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); 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);
});
}
}
} }

View File

@ -179,7 +179,7 @@ export class ProviderLDAPComponent {
} }
public submitForm(): void { public submitForm(): void {
this.provider ? this.updateLDAPProvider() : this.addLDAPProvider(); this.provider || this.justCreated$.value ? this.updateLDAPProvider() : this.addLDAPProvider();
} }
public addLDAPProvider(): void { public addLDAPProvider(): void {

View File

@ -1,3 +1,20 @@
export const supportedLanguages = ['de', 'en', 'es', 'fr', 'it', 'ja', 'pl', 'zh', 'bg', 'pt', 'mk', 'cs', 'ru', 'nl', 'sv']; export const supportedLanguages = [
export const supportedLanguagesRegexp: RegExp = /de|en|es|fr|it|ja|pl|zh|bg|pt|mk|cs|ru|nl|sv/; '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'; export const fallbackLanguage: string = 'en';

View File

@ -1367,7 +1367,7 @@
"ALLOWED_SAVED": "Разрешените езици са запазени успешно.", "ALLOWED_SAVED": "Разрешените езици са запазени успешно.",
"OPTIONS": { "OPTIONS": {
"de": "Deutsch", "de": "Deutsch",
"en": "Английски", "en": "English",
"es": "Español", "es": "Español",
"fr": "Français", "fr": "Français",
"it": "Италиано", "it": "Италиано",
@ -1380,7 +1380,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
} }
}, },
"SMTP": { "SMTP": {
@ -1467,6 +1468,8 @@
"USERSCHEMA_DESCRIPTION": "Потребителските схеми позволяват управление на данните за схемите на потребителите. Ако е активиран флагът, ще можете да използвате новото API и неговите функции.", "USERSCHEMA_DESCRIPTION": "Потребителските схеми позволяват управление на данните за схемите на потребителите. Ако е активиран флагът, ще можете да използвате новото API и неговите функции.",
"ACTIONS": "Действия", "ACTIONS": "Действия",
"ACTIONS_DESCRIPTION": "Действия v2 позволяват управление на выполнения на данни и цели. Ако флагът е активиран, ще можете да използвате новия API и неговите функции.", "ACTIONS_DESCRIPTION": "Действия v2 позволяват управление на выполнения на данни и цели. Ако флагът е активиран, ще можете да използвате новия API и неговите функции.",
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 Завършване на сесия",
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "Ако флагът е активиран, ще можете да прекратите единична сесия от UI за вход, като предоставите id_token с `sid` претенция като id_token_hint на крайната точка на end_session. Имайте предвид, че в момента всички сесии от същия потребителски агент (браузър) се прекратяват в UI за вход. Сесиите, управлявани чрез API на сесията, вече позволяват прекратяването на единични сесии.",
"STATES": { "STATES": {
"INHERITED": "Наследено", "INHERITED": "Наследено",
"ENABLED": "Активирано", "ENABLED": "Активирано",
@ -1599,7 +1602,7 @@
"LANGUAGE": "Език", "LANGUAGE": "Език",
"LANGUAGES": { "LANGUAGES": {
"de": "Deutsch", "de": "Deutsch",
"en": "Английски", "en": "English",
"es": "Español", "es": "Español",
"fr": "Français", "fr": "Français",
"it": "Италиано", "it": "Италиано",
@ -1612,7 +1615,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"KEYS": { "KEYS": {
"emailVerificationDoneText": "Проверката на имейл е извършена", "emailVerificationDoneText": "Проверката на имейл е извършена",
@ -2535,7 +2539,7 @@
}, },
"LANGUAGES": { "LANGUAGES": {
"de": "Deutsch", "de": "Deutsch",
"en": "Английски", "en": "English",
"es": "Español", "es": "Español",
"fr": "Français", "fr": "Français",
"it": "Италиано", "it": "Италиано",
@ -2548,7 +2552,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"MEMBER": { "MEMBER": {
"ADD": "Добавяне на мениджър", "ADD": "Добавяне на мениджър",

View File

@ -1381,7 +1381,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
} }
}, },
"SMTP": { "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.", "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": "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.", "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": { "STATES": {
"INHERITED": "Děděno", "INHERITED": "Děděno",
"ENABLED": "Povoleno", "ENABLED": "Povoleno",
@ -1613,7 +1616,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"KEYS": { "KEYS": {
"emailVerificationDoneText": "Ověření e-mailu dokončeno", "emailVerificationDoneText": "Ověření e-mailu dokončeno",
@ -2561,7 +2565,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"MEMBER": { "MEMBER": {
"ADD": "Přidat manažera", "ADD": "Přidat manažera",

View File

@ -1381,7 +1381,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
} }
}, },
"SMTP": { "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.", "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": "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.", "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": { "STATES": {
"INHERITED": "Erben", "INHERITED": "Erben",
"ENABLED": "Aktiviert", "ENABLED": "Aktiviert",
@ -1613,7 +1616,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"KEYS": { "KEYS": {
"emailVerificationDoneText": "Email Verification erfolgreich", "emailVerificationDoneText": "Email Verification erfolgreich",
@ -2552,7 +2556,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"MEMBER": { "MEMBER": {
"ADD": "Manager hinzufügen", "ADD": "Manager hinzufügen",

View File

@ -1381,7 +1381,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
} }
}, },
"SMTP": { "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.", "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": "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.", "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": { "STATES": {
"INHERITED": "Inherit", "INHERITED": "Inherit",
"ENABLED": "Enabled", "ENABLED": "Enabled",
@ -1613,7 +1616,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"KEYS": { "KEYS": {
"emailVerificationDoneText": "Email verification done", "emailVerificationDoneText": "Email verification done",
@ -2577,7 +2581,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"MEMBER": { "MEMBER": {
"ADD": "Add a Manager", "ADD": "Add a Manager",

View File

@ -1382,7 +1382,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
} }
}, },
"SMTP": { "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.", "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": "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.", "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": { "STATES": {
"INHERITED": "Heredado", "INHERITED": "Heredado",
"ENABLED": "Habilitado", "ENABLED": "Habilitado",
@ -1614,7 +1617,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"KEYS": { "KEYS": {
"emailVerificationDoneText": "Verificación de email realizada", "emailVerificationDoneText": "Verificación de email realizada",
@ -2549,7 +2553,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"MEMBER": { "MEMBER": {
"ADD": "Añadir un Mánager", "ADD": "Añadir un Mánager",

View File

@ -1381,7 +1381,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
} }
}, },
"SMTP": { "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.", "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": "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.", "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": { "STATES": {
"INHERITED": "Hérité", "INHERITED": "Hérité",
"ENABLED": "Activé", "ENABLED": "Activé",
@ -1613,7 +1616,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"KEYS": { "KEYS": {
"emailVerificationDoneText": "Vérification de l'e-mail effectuée", "emailVerificationDoneText": "Vérification de l'e-mail effectuée",
@ -2553,7 +2557,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"MEMBER": { "MEMBER": {
"ADD": "Ajouter un responsable", "ADD": "Ajouter un responsable",

File diff suppressed because it is too large Load Diff

View File

@ -1381,7 +1381,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
} }
}, },
"SMTP": { "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à.", "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": "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à.", "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": { "STATES": {
"INHERITED": "Predefinito", "INHERITED": "Predefinito",
"ENABLED": "Abilitato", "ENABLED": "Abilitato",
@ -1613,7 +1616,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"KEYS": { "KEYS": {
"emailVerificationDoneText": "Verifica dell'e-mail terminata con successo.", "emailVerificationDoneText": "Verifica dell'e-mail terminata con successo.",
@ -2553,7 +2557,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"MEMBER": { "MEMBER": {
"ADD": "Aggiungi un manager", "ADD": "Aggiungi un manager",

View File

@ -1381,7 +1381,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
} }
}, },
"SMTP": { "SMTP": {
@ -1468,6 +1469,8 @@
"USERSCHEMA_DESCRIPTION": "ユーザー スキーマを使用すると、ユーザーのデータスキーマを管理できます。フラグが有効になっている場合、新しい APIとその機能を使用できます。", "USERSCHEMA_DESCRIPTION": "ユーザー スキーマを使用すると、ユーザーのデータスキーマを管理できます。フラグが有効になっている場合、新しい APIとその機能を使用できます。",
"ACTIONS": "アクション", "ACTIONS": "アクション",
"ACTIONS_DESCRIPTION": "Actions v2は、データの実行とターゲットを管理できます。フラグが有効になっている場合、新しい APIとその機能を使用できます。", "ACTIONS_DESCRIPTION": "Actions v2は、データの実行とターゲットを管理できます。フラグが有効になっている場合、新しい APIとその機能を使用できます。",
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 セッション終了",
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "フラグが有効になっている場合、id_token を `sid` クレームと共に id_token_hint として end_session エンドポイントに提供することで、ログイン UI から単一のセッションを終了できるようになります。 現在、同じユーザー エージェント (ブラウザ) からのすべてのセッションがログイン UI で終了することに注意してください。 セッション API を通じて管理されるセッションは、すでに単一のセッションの終了を許可しています。",
"STATES": { "STATES": {
"INHERITED": "継承", "INHERITED": "継承",
"ENABLED": "有効", "ENABLED": "有効",
@ -1609,7 +1612,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"KEYS": { "KEYS": {
"emailVerificationDoneText": "メール認証が完了しました", "emailVerificationDoneText": "メール認証が完了しました",
@ -2544,7 +2548,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"MEMBER": { "MEMBER": {
"ADD": "マネージャーを追加する", "ADD": "マネージャーを追加する",

View File

@ -1382,7 +1382,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
} }
}, },
"SMTP": { "SMTP": {
@ -1469,6 +1470,8 @@
"USERSCHEMA_DESCRIPTION": "Корисничките шеми овозможуваат управување со податоци шеми на корисникот. Ако знамето е овозможено, ќе можете да го користите новиот API и неговите функции.", "USERSCHEMA_DESCRIPTION": "Корисничките шеми овозможуваат управување со податоци шеми на корисникот. Ако знамето е овозможено, ќе можете да го користите новиот API и неговите функции.",
"ACTIONS": "Акции", "ACTIONS": "Акции",
"ACTIONS_DESCRIPTION": "Акциите v2 овозможуваат управување со извршување на податоци и цели. Ако знамето е овозможено, ќе можете да го користите новиот API и неговите функции.", "ACTIONS_DESCRIPTION": "Акциите v2 овозможуваат управување со извршување на податоци и цели. Ако знамето е овозможено, ќе можете да го користите новиот API и неговите функции.",
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 Завршување на сесија",
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "Ако ознаката е активирана, ќе можете да ја завршите единечна сесија од корисничкиот интерфејс за најава, со обезбедување id_token со `sid` побарување како id_token_hint на крајната точка на end_session. Имајте предвид дека во моментов сите сесии од истиот кориснички агент (прелистувач) се завршуваат во корисничкиот интерфејс за најава. Сесиите управувани преку API на сесија веќе дозволуваат завршување на единечни сесии.",
"STATES": { "STATES": {
"INHERITED": "Наследи", "INHERITED": "Наследи",
"ENABLED": "Овозможено", "ENABLED": "Овозможено",
@ -1614,7 +1617,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"KEYS": { "KEYS": {
"emailVerificationDoneText": "Е-поштата е верифицирана", "emailVerificationDoneText": "Е-поштата е верифицирана",
@ -2549,7 +2553,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"MEMBER": { "MEMBER": {
"ADD": "Додај Менаџер", "ADD": "Додај Менаџер",

View File

@ -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.", "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": "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.", "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": { "STATES": {
"INHERITED": "Overgenomen", "INHERITED": "Overgenomen",
"ENABLED": "Ingeschakeld", "ENABLED": "Ingeschakeld",
@ -1613,7 +1615,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"KEYS": { "KEYS": {
"emailVerificationDoneText": "E-mail verificatie voltooid", "emailVerificationDoneText": "E-mail verificatie voltooid",
@ -2570,7 +2573,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"MEMBER": { "MEMBER": {
"ADD": "Voeg een Manager toe", "ADD": "Voeg een Manager toe",

View File

@ -1380,7 +1380,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
} }
}, },
"SMTP": { "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.", "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": "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.", "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": { "STATES": {
"INHERITED": "Dziedziczony", "INHERITED": "Dziedziczony",
"ENABLED": "Włączony", "ENABLED": "Włączony",
@ -1612,7 +1615,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"KEYS": { "KEYS": {
"emailVerificationDoneText": "Weryfikacja adresu e-mail zakończona", "emailVerificationDoneText": "Weryfikacja adresu e-mail zakończona",
@ -2552,7 +2556,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"MEMBER": { "MEMBER": {
"ADD": "Dodaj managera", "ADD": "Dodaj managera",

View File

@ -1382,7 +1382,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
} }
}, },
"SMTP": { "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.", "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": "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.", "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": { "STATES": {
"INHERITED": "Herdade", "INHERITED": "Herdade",
"ENABLED": "Habilitado", "ENABLED": "Habilitado",
@ -1614,7 +1617,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"KEYS": { "KEYS": {
"emailVerificationDoneText": "Verificação de email concluída", "emailVerificationDoneText": "Verificação de email concluída",
@ -2547,7 +2551,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"MEMBER": { "MEMBER": {
"ADD": "Adicionar um Gerente", "ADD": "Adicionar um Gerente",

View File

@ -1422,9 +1422,11 @@
"bg": "Български", "bg": "Български",
"pt": "Portuguese", "pt": "Portuguese",
"mk": "Македонски", "mk": "Македонски",
"cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
} }
}, },
"SMTP": { "SMTP": {
@ -1519,6 +1521,8 @@
"USERSCHEMA_DESCRIPTION": "Схемы пользователей позволяют управлять схемами данных пользователей. Если флаг включен, вы сможете использовать новый API и его функции.", "USERSCHEMA_DESCRIPTION": "Схемы пользователей позволяют управлять схемами данных пользователей. Если флаг включен, вы сможете использовать новый API и его функции.",
"ACTIONS": "Действия", "ACTIONS": "Действия",
"ACTIONS_DESCRIPTION": "Actions v2 позволяют управлять выполнением данных и целевыми объектами. Если флаг включен, вы сможете использовать новый API и его функции.", "ACTIONS_DESCRIPTION": "Actions v2 позволяют управлять выполнением данных и целевыми объектами. Если флаг включен, вы сможете использовать новый API и его функции.",
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 Окончание сеанса",
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "Если флаг включен, вы сможете завершить отдельный сеанс из интерфейса пользователя входа, предоставив id_token с претензией `sid` в качестве id_token_hint на конечной точке end_session. Обратите внимание, что в настоящее время все сеансы одного и того же пользовательского агента (браузера) завершаются в интерфейсе пользователя входа. Сеансы, управляемые через API сеанса, уже позволяют завершать отдельные сеансы.",
"STATES": { "STATES": {
"INHERITED": "Наследовать", "INHERITED": "Наследовать",
"ENABLED": "Включено", "ENABLED": "Включено",
@ -1668,7 +1672,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"LOCALE": "Код языка", "LOCALE": "Код языка",
"LOCALES": { "LOCALES": {
@ -2655,9 +2660,11 @@
"bg": "Български", "bg": "Български",
"pt": "Portuguese", "pt": "Portuguese",
"mk": "Македонски", "mk": "Македонски",
"cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"MEMBER": { "MEMBER": {
"ADD": "Добавить менеджера", "ADD": "Добавить менеджера",

View File

@ -1385,7 +1385,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
} }
}, },
"SMTP": { "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.", "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": "Å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.", "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": { "STATES": {
"INHERITED": "Ärv", "INHERITED": "Ärv",
"ENABLED": "Aktiverad", "ENABLED": "Aktiverad",
@ -1617,7 +1620,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"KEYS": { "KEYS": {
"emailVerificationDoneText": "E-postverifiering klar", "emailVerificationDoneText": "E-postverifiering klar",
@ -2576,12 +2580,13 @@
"pl": "Polski", "pl": "Polski",
"zh": "简体中文", "zh": "简体中文",
"bg": "Български", "bg": "Български",
"pt": "Português", "pt": "Portuguese",
"mk": "Македонски", "mk": "Македонски",
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"MEMBER": { "MEMBER": {
"ADD": "Lägg till en administratör", "ADD": "Lägg till en administratör",

View File

@ -1381,7 +1381,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
} }
}, },
"SMTP": { "SMTP": {
@ -1468,6 +1469,8 @@
"USERSCHEMA_DESCRIPTION": "用户架构允许管理用户的数据架构。如果启用此标志,您将可以使用新的 API 及其功能。", "USERSCHEMA_DESCRIPTION": "用户架构允许管理用户的数据架构。如果启用此标志,您将可以使用新的 API 及其功能。",
"ACTIONS": "操作", "ACTIONS": "操作",
"ACTIONS_DESCRIPTION": "Actions v2 可以管理数据执行和目标。如果启用此标志,您将可以使用新的 API 及其功能。", "ACTIONS_DESCRIPTION": "Actions v2 可以管理数据执行和目标。如果启用此标志,您将可以使用新的 API 及其功能。",
"OIDCSINGLEV1SESSIONTERMINATION": "OIDC Single V1 终止会话",
"OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION": "如果启用了标志,您可以通过在 end_session 端点上提供带有 `sid` 声明的 id_token 作为 id_token_hint 来从登录 UI 终止单个会话。 请注意,目前所有来自同一用户代理(浏览器)的会话都在登录 UI 中终止。 通过会话 API 管理的会话已经允许终止单个会话。",
"STATES": { "STATES": {
"INHERITED": "继承", "INHERITED": "继承",
"ENABLED": "已启用", "ENABLED": "已启用",
@ -1612,7 +1615,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"KEYS": { "KEYS": {
"emailVerificationDoneText": "电子邮件验证完成", "emailVerificationDoneText": "电子邮件验证完成",
@ -2552,7 +2556,8 @@
"cs": "Čeština", "cs": "Čeština",
"ru": "Русский", "ru": "Русский",
"nl": "Nederlands", "nl": "Nederlands",
"sv": "Svenska" "sv": "Svenska",
"id": "Bahasa Indonesia"
}, },
"MEMBER": { "MEMBER": {
"ADD": "添加管理者", "ADD": "添加管理者",

View File

@ -38,6 +38,7 @@ ZITADEL is available in the following languages
- English (en) - English (en)
- Spanish (es) - Spanish (es)
- French (fr) - French (fr)
- Indonesian (id)
- Italian (it) - Italian (it)
- 日本語 (ja) - 日本語 (ja)
- Polishpl - Polishpl

View File

@ -17,6 +17,7 @@ func systemFeaturesToCommand(req *feature_pb.SetSystemFeaturesRequest) *command.
Actions: req.Actions, Actions: req.Actions,
TokenExchange: req.OidcTokenExchange, TokenExchange: req.OidcTokenExchange,
ImprovedPerformance: improvedPerformanceListToDomain(req.ImprovedPerformance), ImprovedPerformance: improvedPerformanceListToDomain(req.ImprovedPerformance),
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
} }
} }
@ -30,6 +31,7 @@ func systemFeaturesToPb(f *query.SystemFeatures) *feature_pb.GetSystemFeaturesRe
OidcTokenExchange: featureSourceToFlagPb(&f.TokenExchange), OidcTokenExchange: featureSourceToFlagPb(&f.TokenExchange),
Actions: featureSourceToFlagPb(&f.Actions), Actions: featureSourceToFlagPb(&f.Actions),
ImprovedPerformance: featureSourceToImprovedPerformanceFlagPb(&f.ImprovedPerformance), ImprovedPerformance: featureSourceToImprovedPerformanceFlagPb(&f.ImprovedPerformance),
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
} }
} }
@ -44,6 +46,7 @@ func instanceFeaturesToCommand(req *feature_pb.SetInstanceFeaturesRequest) *comm
ImprovedPerformance: improvedPerformanceListToDomain(req.ImprovedPerformance), ImprovedPerformance: improvedPerformanceListToDomain(req.ImprovedPerformance),
WebKey: req.WebKey, WebKey: req.WebKey,
DebugOIDCParentError: req.DebugOidcParentError, DebugOIDCParentError: req.DebugOidcParentError,
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
} }
} }
@ -59,6 +62,7 @@ func instanceFeaturesToPb(f *query.InstanceFeatures) *feature_pb.GetInstanceFeat
ImprovedPerformance: featureSourceToImprovedPerformanceFlagPb(&f.ImprovedPerformance), ImprovedPerformance: featureSourceToImprovedPerformanceFlagPb(&f.ImprovedPerformance),
WebKey: featureSourceToFlagPb(&f.WebKey), WebKey: featureSourceToFlagPb(&f.WebKey),
DebugOidcParentError: featureSourceToFlagPb(&f.DebugOIDCParentError), DebugOidcParentError: featureSourceToFlagPb(&f.DebugOIDCParentError),
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
} }
} }

View File

@ -25,6 +25,7 @@ func Test_systemFeaturesToCommand(t *testing.T) {
Actions: gu.Ptr(true), Actions: gu.Ptr(true),
OidcTokenExchange: gu.Ptr(true), OidcTokenExchange: gu.Ptr(true),
ImprovedPerformance: nil, ImprovedPerformance: nil,
OidcSingleV1SessionTermination: gu.Ptr(true),
} }
want := &command.SystemFeatures{ want := &command.SystemFeatures{
LoginDefaultOrg: gu.Ptr(true), LoginDefaultOrg: gu.Ptr(true),
@ -34,6 +35,7 @@ func Test_systemFeaturesToCommand(t *testing.T) {
Actions: gu.Ptr(true), Actions: gu.Ptr(true),
TokenExchange: gu.Ptr(true), TokenExchange: gu.Ptr(true),
ImprovedPerformance: nil, ImprovedPerformance: nil,
OIDCSingleV1SessionTermination: gu.Ptr(true),
} }
got := systemFeaturesToCommand(arg) got := systemFeaturesToCommand(arg)
assert.Equal(t, want, got) assert.Equal(t, want, got)
@ -74,6 +76,10 @@ func Test_systemFeaturesToPb(t *testing.T) {
Level: feature.LevelSystem, Level: feature.LevelSystem,
Value: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgByID}, Value: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgByID},
}, },
OIDCSingleV1SessionTermination: query.FeatureSource[bool]{
Level: feature.LevelSystem,
Value: true,
},
} }
want := &feature_pb.GetSystemFeaturesResponse{ want := &feature_pb.GetSystemFeaturesResponse{
Details: &object.Details{ 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}, ExecutionPaths: []feature_pb.ImprovedPerformance{feature_pb.ImprovedPerformance_IMPROVED_PERFORMANCE_ORG_BY_ID},
Source: feature_pb.Source_SOURCE_SYSTEM, Source: feature_pb.Source_SOURCE_SYSTEM,
}, },
OidcSingleV1SessionTermination: &feature_pb.FeatureFlag{
Enabled: true,
Source: feature_pb.Source_SOURCE_SYSTEM,
},
} }
got := systemFeaturesToPb(arg) got := systemFeaturesToPb(arg)
assert.Equal(t, want, got) assert.Equal(t, want, got)
@ -124,6 +134,8 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
Actions: gu.Ptr(true), Actions: gu.Ptr(true),
ImprovedPerformance: nil, ImprovedPerformance: nil,
WebKey: gu.Ptr(true), WebKey: gu.Ptr(true),
DebugOidcParentError: gu.Ptr(true),
OidcSingleV1SessionTermination: gu.Ptr(true),
} }
want := &command.InstanceFeatures{ want := &command.InstanceFeatures{
LoginDefaultOrg: gu.Ptr(true), LoginDefaultOrg: gu.Ptr(true),
@ -134,6 +146,8 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
Actions: gu.Ptr(true), Actions: gu.Ptr(true),
ImprovedPerformance: nil, ImprovedPerformance: nil,
WebKey: gu.Ptr(true), WebKey: gu.Ptr(true),
DebugOIDCParentError: gu.Ptr(true),
OIDCSingleV1SessionTermination: gu.Ptr(true),
} }
got := instanceFeaturesToCommand(arg) got := instanceFeaturesToCommand(arg)
assert.Equal(t, want, got) assert.Equal(t, want, got)
@ -178,6 +192,10 @@ func Test_instanceFeaturesToPb(t *testing.T) {
Level: feature.LevelInstance, Level: feature.LevelInstance,
Value: true, Value: true,
}, },
OIDCSingleV1SessionTermination: query.FeatureSource[bool]{
Level: feature.LevelInstance,
Value: true,
},
} }
want := &feature_pb.GetInstanceFeaturesResponse{ want := &feature_pb.GetInstanceFeaturesResponse{
Details: &object.Details{ Details: &object.Details{
@ -221,6 +239,10 @@ func Test_instanceFeaturesToPb(t *testing.T) {
Enabled: false, Enabled: false,
Source: feature_pb.Source_SOURCE_UNSPECIFIED, Source: feature_pb.Source_SOURCE_UNSPECIFIED,
}, },
OidcSingleV1SessionTermination: &feature_pb.FeatureFlag{
Enabled: true,
Source: feature_pb.Source_SOURCE_INSTANCE,
},
} }
got := instanceFeaturesToPb(arg) got := instanceFeaturesToPb(arg)
assert.Equal(t, want, got) assert.Equal(t, want, got)

View File

@ -17,6 +17,7 @@ func systemFeaturesToCommand(req *feature_pb.SetSystemFeaturesRequest) *command.
Actions: req.Actions, Actions: req.Actions,
TokenExchange: req.OidcTokenExchange, TokenExchange: req.OidcTokenExchange,
ImprovedPerformance: improvedPerformanceListToDomain(req.ImprovedPerformance), ImprovedPerformance: improvedPerformanceListToDomain(req.ImprovedPerformance),
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
} }
} }
@ -30,6 +31,7 @@ func systemFeaturesToPb(f *query.SystemFeatures) *feature_pb.GetSystemFeaturesRe
OidcTokenExchange: featureSourceToFlagPb(&f.TokenExchange), OidcTokenExchange: featureSourceToFlagPb(&f.TokenExchange),
Actions: featureSourceToFlagPb(&f.Actions), Actions: featureSourceToFlagPb(&f.Actions),
ImprovedPerformance: featureSourceToImprovedPerformanceFlagPb(&f.ImprovedPerformance), ImprovedPerformance: featureSourceToImprovedPerformanceFlagPb(&f.ImprovedPerformance),
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
} }
} }
@ -44,6 +46,7 @@ func instanceFeaturesToCommand(req *feature_pb.SetInstanceFeaturesRequest) *comm
ImprovedPerformance: improvedPerformanceListToDomain(req.ImprovedPerformance), ImprovedPerformance: improvedPerformanceListToDomain(req.ImprovedPerformance),
WebKey: req.WebKey, WebKey: req.WebKey,
DebugOIDCParentError: req.DebugOidcParentError, DebugOIDCParentError: req.DebugOidcParentError,
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
} }
} }
@ -59,6 +62,7 @@ func instanceFeaturesToPb(f *query.InstanceFeatures) *feature_pb.GetInstanceFeat
ImprovedPerformance: featureSourceToImprovedPerformanceFlagPb(&f.ImprovedPerformance), ImprovedPerformance: featureSourceToImprovedPerformanceFlagPb(&f.ImprovedPerformance),
WebKey: featureSourceToFlagPb(&f.WebKey), WebKey: featureSourceToFlagPb(&f.WebKey),
DebugOidcParentError: featureSourceToFlagPb(&f.DebugOIDCParentError), DebugOidcParentError: featureSourceToFlagPb(&f.DebugOIDCParentError),
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
} }
} }

View File

@ -25,6 +25,7 @@ func Test_systemFeaturesToCommand(t *testing.T) {
Actions: gu.Ptr(true), Actions: gu.Ptr(true),
OidcTokenExchange: gu.Ptr(true), OidcTokenExchange: gu.Ptr(true),
ImprovedPerformance: nil, ImprovedPerformance: nil,
OidcSingleV1SessionTermination: gu.Ptr(true),
} }
want := &command.SystemFeatures{ want := &command.SystemFeatures{
LoginDefaultOrg: gu.Ptr(true), LoginDefaultOrg: gu.Ptr(true),
@ -34,6 +35,7 @@ func Test_systemFeaturesToCommand(t *testing.T) {
Actions: gu.Ptr(true), Actions: gu.Ptr(true),
TokenExchange: gu.Ptr(true), TokenExchange: gu.Ptr(true),
ImprovedPerformance: nil, ImprovedPerformance: nil,
OIDCSingleV1SessionTermination: gu.Ptr(true),
} }
got := systemFeaturesToCommand(arg) got := systemFeaturesToCommand(arg)
assert.Equal(t, want, got) assert.Equal(t, want, got)
@ -74,6 +76,10 @@ func Test_systemFeaturesToPb(t *testing.T) {
Level: feature.LevelSystem, Level: feature.LevelSystem,
Value: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgByID}, Value: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgByID},
}, },
OIDCSingleV1SessionTermination: query.FeatureSource[bool]{
Level: feature.LevelSystem,
Value: true,
},
} }
want := &feature_pb.GetSystemFeaturesResponse{ want := &feature_pb.GetSystemFeaturesResponse{
Details: &object.Details{ 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}, ExecutionPaths: []feature_pb.ImprovedPerformance{feature_pb.ImprovedPerformance_IMPROVED_PERFORMANCE_ORG_BY_ID},
Source: feature_pb.Source_SOURCE_SYSTEM, Source: feature_pb.Source_SOURCE_SYSTEM,
}, },
OidcSingleV1SessionTermination: &feature_pb.FeatureFlag{
Enabled: true,
Source: feature_pb.Source_SOURCE_SYSTEM,
},
} }
got := systemFeaturesToPb(arg) got := systemFeaturesToPb(arg)
assert.Equal(t, want, got) assert.Equal(t, want, got)
@ -124,6 +134,7 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
Actions: gu.Ptr(true), Actions: gu.Ptr(true),
ImprovedPerformance: nil, ImprovedPerformance: nil,
WebKey: gu.Ptr(true), WebKey: gu.Ptr(true),
OidcSingleV1SessionTermination: gu.Ptr(true),
} }
want := &command.InstanceFeatures{ want := &command.InstanceFeatures{
LoginDefaultOrg: gu.Ptr(true), LoginDefaultOrg: gu.Ptr(true),
@ -134,6 +145,7 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
Actions: gu.Ptr(true), Actions: gu.Ptr(true),
ImprovedPerformance: nil, ImprovedPerformance: nil,
WebKey: gu.Ptr(true), WebKey: gu.Ptr(true),
OIDCSingleV1SessionTermination: gu.Ptr(true),
} }
got := instanceFeaturesToCommand(arg) got := instanceFeaturesToCommand(arg)
assert.Equal(t, want, got) assert.Equal(t, want, got)
@ -178,6 +190,10 @@ func Test_instanceFeaturesToPb(t *testing.T) {
Level: feature.LevelInstance, Level: feature.LevelInstance,
Value: true, Value: true,
}, },
OIDCSingleV1SessionTermination: query.FeatureSource[bool]{
Level: feature.LevelInstance,
Value: true,
},
} }
want := &feature_pb.GetInstanceFeaturesResponse{ want := &feature_pb.GetInstanceFeaturesResponse{
Details: &object.Details{ Details: &object.Details{
@ -221,6 +237,10 @@ func Test_instanceFeaturesToPb(t *testing.T) {
Enabled: false, Enabled: false,
Source: feature_pb.Source_SOURCE_UNSPECIFIED, Source: feature_pb.Source_SOURCE_UNSPECIFIED,
}, },
OidcSingleV1SessionTermination: &feature_pb.FeatureFlag{
Enabled: true,
Source: feature_pb.Source_SOURCE_INSTANCE,
},
} }
got := instanceFeaturesToPb(arg) got := instanceFeaturesToPb(arg)
assert.Equal(t, want, got) assert.Equal(t, want, got)

View File

@ -16,6 +16,7 @@ import (
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
http_utils "github.com/zitadel/zitadel/internal/api/http" http_utils "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/http/middleware" "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/command"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query" "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, // 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 == "" { if endSessionRequest.IDTokenHintClaims == nil || endSessionRequest.IDTokenHintClaims.SessionID == "" {
return endSessionRequest.RedirectURI, o.TerminateSession(ctx, endSessionRequest.UserID, endSessionRequest.ClientID) 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 // terminate the v2 session of the id_token_hint
_, err = o.command.TerminateSessionWithoutTokenCheck(ctx, endSessionRequest.IDTokenHintClaims.SessionID) _, err = o.command.TerminateSessionWithoutTokenCheck(ctx, endSessionRequest.IDTokenHintClaims.SessionID)
if err != nil { if err != nil {
@ -258,6 +268,30 @@ func (o *OPStorage) TerminateSessionFromRequest(ctx context.Context, endSessionR
return endSessionRequest.RedirectURI, nil 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) { func (o *OPStorage) RevokeToken(ctx context.Context, token, userID, clientID string) (err *oidc.Error) {
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { defer func() {
@ -564,6 +598,7 @@ func (s *Server) authResponseToken(authReq *AuthRequest, authorizer op.Authorize
domain.TokenReasonAuthRequest, domain.TokenReasonAuthRequest,
nil, nil,
slices.Contains(scope, oidc.ScopeOfflineAccess), slices.Contains(scope, oidc.ScopeOfflineAccess),
authReq.SessionID,
) )
if err != nil { if err != nil {
op.AuthRequestError(w, r, authReq, err, authorizer) op.AuthRequestError(w, r, authReq, err, authorizer)

View File

@ -45,6 +45,7 @@ func (s *Server) ClientCredentialsExchange(ctx context.Context, r *op.ClientRequ
domain.TokenReasonClientCredentials, domain.TokenReasonClientCredentials,
nil, nil,
false, false,
"",
) )
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -85,6 +85,7 @@ func (s *Server) codeExchangeV1(ctx context.Context, client *Client, req *oidc.A
domain.TokenReasonAuthRequest, domain.TokenReasonAuthRequest,
nil, nil,
slices.Contains(scope, oidc.ScopeOfflineAccess), slices.Contains(scope, oidc.ScopeOfflineAccess),
authReq.SessionID,
) )
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -298,6 +298,7 @@ func (s *Server) createExchangeAccessToken(
reason, reason,
actor, actor,
slices.Contains(scope, oidc.ScopeOfflineAccess), slices.Contains(scope, oidc.ScopeOfflineAccess),
"",
) )
if err != nil { if err != nil {
return "", "", "", 0, err return "", "", "", 0, err
@ -342,6 +343,7 @@ func (s *Server) createExchangeJWT(
reason, reason,
actor, actor,
slices.Contains(scope, oidc.ScopeOfflineAccess), slices.Contains(scope, oidc.ScopeOfflineAccess),
"",
) )
accessToken, err = s.createJWT(ctx, client, session, getUserInfo, roleAssertion, getSigner) accessToken, err = s.createJWT(ctx, client, session, getUserInfo, roleAssertion, getSigner)
if err != nil { if err != nil {

View File

@ -53,6 +53,7 @@ func (s *Server) JWTProfile(ctx context.Context, r *op.Request[oidc.JWTProfileGr
domain.TokenReasonJWTProfile, domain.TokenReasonJWTProfile,
nil, nil,
false, false,
"",
) )
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -67,6 +67,7 @@ func (s *Server) refreshTokenV1(ctx context.Context, client *Client, r *op.Clien
domain.TokenReasonRefresh, domain.TokenReasonRefresh,
refreshToken.Actor, refreshToken.Actor,
true, true,
"",
) )
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -162,7 +162,7 @@ func (l *Login) handleDeviceAuthAction(w http.ResponseWriter, r *http.Request) {
action := mux.Vars(r)["action"] action := mux.Vars(r)["action"]
switch action { switch action {
case deviceAuthAllowed: 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: case deviceAuthDenied:
_, err = l.command.CancelDeviceAuth(r.Context(), authDev.DeviceCode, domain.DeviceAuthCanceledDenied) _, err = l.command.CancelDeviceAuth(r.Context(), authDev.DeviceCode, domain.DeviceAuthCanceledDenied)
default: default:

View File

@ -250,6 +250,7 @@ RegistrationUser:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
GenderLabel: Пол GenderLabel: Пол
Female: Женски пол Female: Женски пол
Male: Мъжки Male: Мъжки
@ -289,6 +290,7 @@ ExternalRegistrationUserOverview:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
TosAndPrivacyLabel: Правила и условия TosAndPrivacyLabel: Правила и условия
TosConfirm: Приемам TosConfirm: Приемам
TosLinkText: TOS TosLinkText: TOS
@ -357,6 +359,7 @@ ExternalNotFound:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
DeviceAuth: DeviceAuth:
Title: Упълномощаване на устройството Title: Упълномощаване на устройството
UserCode: UserCode:

View File

@ -253,6 +253,7 @@ RegistrationUser:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
GenderLabel: Pohlaví GenderLabel: Pohlaví
Female: Žena Female: Žena
Male: Muž Male: Muž
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
TosAndPrivacyLabel: Obchodní podmínky TosAndPrivacyLabel: Obchodní podmínky
TosConfirm: Souhlasím s TosConfirm: Souhlasím s
TosLinkText: obchodními podmínkami TosLinkText: obchodními podmínkami
@ -367,6 +369,7 @@ ExternalNotFound:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
DeviceAuth: DeviceAuth:
Title: Autorizace zařízení Title: Autorizace zařízení
UserCode: UserCode:

View File

@ -252,6 +252,7 @@ RegistrationUser:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
GenderLabel: Geschlecht GenderLabel: Geschlecht
Female: weiblich Female: weiblich
Male: männlich Male: männlich
@ -292,6 +293,7 @@ ExternalRegistrationUserOverview:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
TosAndPrivacyLabel: Allgemeine Geschäftsbedingungen und Datenschutz TosAndPrivacyLabel: Allgemeine Geschäftsbedingungen und Datenschutz
TosConfirm: Ich akzeptiere die TosConfirm: Ich akzeptiere die
TosLinkText: AGB TosLinkText: AGB
@ -366,6 +368,7 @@ ExternalNotFound:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
DeviceAuth: DeviceAuth:
Title: Gerät verbinden Title: Gerät verbinden
UserCode: UserCode:

View File

@ -253,6 +253,7 @@ RegistrationUser:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
GenderLabel: Gender GenderLabel: Gender
Female: Female Female: Female
Male: Male Male: Male
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
TosAndPrivacyLabel: Terms and conditions TosAndPrivacyLabel: Terms and conditions
TosConfirm: I accept the TosConfirm: I accept the
TosLinkText: TOS TosLinkText: TOS
@ -367,6 +369,7 @@ ExternalNotFound:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
DeviceAuth: DeviceAuth:
Title: Device Authorization Title: Device Authorization
UserCode: UserCode:

View File

@ -253,6 +253,7 @@ RegistrationUser:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
GenderLabel: Género GenderLabel: Género
Female: Mujer Female: Mujer
Male: Hombre Male: Hombre
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
TosAndPrivacyLabel: Términos y condiciones TosAndPrivacyLabel: Términos y condiciones
TosConfirm: Acepto los TosConfirm: Acepto los
TosLinkText: TDS TosLinkText: TDS
@ -367,6 +369,7 @@ ExternalNotFound:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
Footer: Footer:
PoweredBy: Powered By PoweredBy: Powered By

View File

@ -253,6 +253,7 @@ RegistrationUser:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
GenderLabel: Genre GenderLabel: Genre
Female: Femme Female: Femme
Male: Homme Male: Homme
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
TosAndPrivacyLabel: Termes et conditions TosAndPrivacyLabel: Termes et conditions
TosConfirm: J'accepte les TosConfirm: J'accepte les
TosLinkText: TOS TosLinkText: TOS
@ -367,6 +369,7 @@ ExternalNotFound:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
DeviceAuth: DeviceAuth:
Title: Autorisation de l'appareil Title: Autorisation de l'appareil

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

View File

@ -253,6 +253,7 @@ RegistrationUser:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
GenderLabel: Genere GenderLabel: Genere
Female: Femminile Female: Femminile
Male: Maschile Male: Maschile
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
TosAndPrivacyLabel: Termini di servizio TosAndPrivacyLabel: Termini di servizio
TosConfirm: Accetto i TosConfirm: Accetto i
TosLinkText: Termini di servizio TosLinkText: Termini di servizio
@ -367,6 +369,7 @@ ExternalNotFound:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
DeviceAuth: DeviceAuth:
Title: Autorizzazione del dispositivo Title: Autorizzazione del dispositivo

View File

@ -245,6 +245,7 @@ RegistrationUser:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
GenderLabel: 性別 GenderLabel: 性別
Female: 女性 Female: 女性
Male: 男性 Male: 男性
@ -285,6 +286,7 @@ ExternalRegistrationUserOverview:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
TosAndPrivacyLabel: 利用規約 TosAndPrivacyLabel: 利用規約
TosConfirm: 私は利用規約を承諾します。 TosConfirm: 私は利用規約を承諾します。
TosLinkText: TOS TosLinkText: TOS
@ -359,6 +361,7 @@ ExternalNotFound:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
DeviceAuth: DeviceAuth:
Title: デバイス認証 Title: デバイス認証

View File

@ -253,6 +253,7 @@ RegistrationUser:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
GenderLabel: Пол GenderLabel: Пол
Female: Женски Female: Женски
Male: Машки Male: Машки
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
TosAndPrivacyLabel: Правила и услови TosAndPrivacyLabel: Правила и услови
TosConfirm: Се согласувам со TosConfirm: Се согласувам со
TosLinkText: правилата за користење TosLinkText: правилата за користење
@ -367,6 +369,7 @@ ExternalNotFound:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
DeviceAuth: DeviceAuth:
Title: Овластување преку уред Title: Овластување преку уред

View File

@ -253,6 +253,7 @@ RegistrationUser:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
GenderLabel: Geslacht GenderLabel: Geslacht
Female: Vrouw Female: Vrouw
Male: Man Male: Man
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
Russian: Русский Russian: Русский
Nederlands: Nederlands Nederlands: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
TosAndPrivacyLabel: Algemene voorwaarden TosAndPrivacyLabel: Algemene voorwaarden
TosConfirm: Ik accepteer de TosConfirm: Ik accepteer de
TosLinkText: AV TosLinkText: AV
@ -367,6 +369,7 @@ ExternalNotFound:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
DeviceAuth: DeviceAuth:
Title: Apparaat Autorisatie Title: Apparaat Autorisatie
UserCode: UserCode:

View File

@ -253,6 +253,7 @@ RegistrationUser:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
GenderLabel: Płeć GenderLabel: Płeć
Female: Kobieta Female: Kobieta
Male: Mężczyzna Male: Mężczyzna
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
TosAndPrivacyLabel: Warunki i zasady TosAndPrivacyLabel: Warunki i zasady
TosConfirm: Akceptuję TosConfirm: Akceptuję
TosLinkText: Warunki korzystania TosLinkText: Warunki korzystania
@ -367,6 +369,7 @@ ExternalNotFound:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
DeviceAuth: DeviceAuth:
Title: Autoryzacja urządzenia Title: Autoryzacja urządzenia

View File

@ -249,6 +249,7 @@ RegistrationUser:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
GenderLabel: Gênero GenderLabel: Gênero
Female: Feminino Female: Feminino
Male: Masculino Male: Masculino
@ -289,6 +290,7 @@ ExternalRegistrationUserOverview:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
TosAndPrivacyLabel: Termos e condições TosAndPrivacyLabel: Termos e condições
TosConfirm: Eu aceito os TosConfirm: Eu aceito os
TosLinkText: termos de serviço TosLinkText: termos de serviço
@ -363,6 +365,7 @@ ExternalNotFound:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
DeviceAuth: DeviceAuth:
Title: Autorização de dispositivo Title: Autorização de dispositivo

View File

@ -252,6 +252,7 @@ RegistrationUser:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
GenderLabel: Пол GenderLabel: Пол
Female: Женский Female: Женский
Male: Мужской Male: Мужской
@ -292,6 +293,7 @@ ExternalRegistrationUserOverview:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
TosAndPrivacyLabel: Условия использования TosAndPrivacyLabel: Условия использования
TosConfirm: Я согласен с TosConfirm: Я согласен с
TosLinkText: Пользовательским соглашением TosLinkText: Пользовательским соглашением
@ -366,6 +368,7 @@ ExternalNotFound:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
DeviceAuth: DeviceAuth:
Title: Авторизация устройства Title: Авторизация устройства

View File

@ -253,6 +253,7 @@ RegistrationUser:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
GenderLabel: Kön GenderLabel: Kön
Female: Man Female: Man
Male: Kvinna Male: Kvinna
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
TosAndPrivacyLabel: Användarvillkor TosAndPrivacyLabel: Användarvillkor
TosConfirm: Jag accepterar TosConfirm: Jag accepterar
TosLinkText: Användarvillkoren TosLinkText: Användarvillkoren
@ -367,6 +369,7 @@ ExternalNotFound:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
DeviceAuth: DeviceAuth:
Title: Tillgång från hårdvaruenhet Title: Tillgång från hårdvaruenhet
UserCode: UserCode:

View File

@ -253,6 +253,7 @@ RegistrationUser:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
GenderLabel: 性别 GenderLabel: 性别
Female: 女性 Female: 女性
Male: 男性 Male: 男性
@ -293,6 +294,7 @@ ExternalRegistrationUserOverview:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
TosAndPrivacyLabel: 条款和条款 TosAndPrivacyLabel: 条款和条款
TosConfirm: 我接受 TosConfirm: 我接受
TosLinkText: 服务条款 TosLinkText: 服务条款
@ -367,6 +369,7 @@ ExternalNotFound:
Russian: Русский Russian: Русский
Dutch: Nederlands Dutch: Nederlands
Swedish: Svenska Swedish: Svenska
Indonesian: Bahasa Indonesia
DeviceAuth: DeviceAuth:
Title: 设备授权 Title: 设备授权
UserCode: UserCode:

View File

@ -72,6 +72,8 @@
</option> </option>
<option value="fr" id="fr" {{if (selectedLanguage "fr")}} selected {{end}}>{{t "ExternalNotFound.French"}} <option value="fr" id="fr" {{if (selectedLanguage "fr")}} selected {{end}}>{{t "ExternalNotFound.French"}}
</option> </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 value="it" id="it" {{if (selectedLanguage "it")}} selected {{end}}>{{t "ExternalNotFound.Italian"}}
</option> </option>
<option value="ja" id="ja" {{if (selectedLanguage "ja")}} selected {{end}}>{{t "ExternalNotFound.Japanese"}} <option value="ja" id="ja" {{if (selectedLanguage "ja")}} selected {{end}}>{{t "ExternalNotFound.Japanese"}}

View File

@ -74,8 +74,8 @@ type privacyPolicyProvider interface {
} }
type userSessionViewProvider interface { type userSessionViewProvider interface {
UserSessionByIDs(string, string, string) (*user_view_model.UserSessionView, error) UserSessionByIDs(context.Context, string, string, string) (*user_view_model.UserSessionView, error)
UserSessionsByAgentID(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) 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 { if err != nil {
return nil, err return nil, err
} }
request.SessionID = userSession.ID
request.DisplayName = userSession.DisplayName request.DisplayName = userSession.DisplayName
request.AvatarKey = userSession.AvatarKey request.AvatarKey = userSession.AvatarKey
if user.HumanView != nil && user.HumanView.PreferredLanguage != "" { if user.HumanView != nil && user.HumanView.PreferredLanguage != "" {
@ -1532,7 +1533,7 @@ func userSessionsByUserAgentID(ctx context.Context, provider userSessionViewProv
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()
session, err := provider.UserSessionsByAgentID(agentID, instanceID) session, err := provider.UserSessionsByAgentID(ctx, agentID, instanceID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1572,7 +1573,7 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve
OnError(err). OnError(err).
Errorf("could not get current sequence for userSessionByIDs") 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 err != nil {
if !zerrors.IsNotFound(err) { if !zerrors.IsNotFound(err) {
return nil, err return nil, err

View File

@ -34,11 +34,11 @@ var (
type mockViewNoUserSession struct{} 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") 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 return nil, nil
} }
@ -48,11 +48,11 @@ func (m *mockViewNoUserSession) GetLatestUserSessionSequence(ctx context.Context
type mockViewErrUserSession struct{} 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") 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") return nil, zerrors.ThrowInternal(nil, "id", "internal error")
} }
@ -76,7 +76,7 @@ type mockUser struct {
SessionState domain.UserSessionState 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{ return &user_view_model.UserSessionView{
ExternalLoginVerification: sql.NullTime{Time: m.ExternalLoginVerification}, ExternalLoginVerification: sql.NullTime{Time: m.ExternalLoginVerification},
PasswordlessVerification: sql.NullTime{Time: m.PasswordlessVerification}, PasswordlessVerification: sql.NullTime{Time: m.PasswordlessVerification},
@ -86,7 +86,7 @@ func (m *mockViewUserSession) UserSessionByIDs(string, string, string) (*user_vi
}, nil }, 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)) sessions := make([]*user_view_model.UserSessionView, len(m.Users))
for i, user := range m.Users { for i, user := range m.Users {
sessions[i] = &user_view_model.UserSessionView{ sessions[i] = &user_view_model.UserSessionView{

View File

@ -28,7 +28,7 @@ func (repo *UserRepo) Health(ctx context.Context) error {
} }
func (repo *UserRepo) UserSessionUserIDsByAgentID(ctx context.Context, agentID string) ([]string, 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 { if err != nil {
return nil, err return nil, err
} }
@ -41,6 +41,14 @@ func (repo *UserRepo) UserSessionUserIDsByAgentID(ctx context.Context, agentID s
return userIDs, nil 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) { 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) query, err := usr_view.UserByIDQuery(id, authz.GetInstance(ctx).InstanceID(), changeDate, eventTypes)
if err != nil { if err != nil {

View File

@ -14,7 +14,7 @@ type UserSessionRepo struct {
} }
func (repo *UserSessionRepo) GetMyUserSessions(ctx context.Context) ([]*usr_model.UserSessionView, error) { 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -9,6 +9,7 @@ import (
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2" "github.com/zitadel/zitadel/internal/eventstore/handler/v2"
handler2 "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" 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"), configs.overwrite("UserSession"),
view, view,
queries, queries,
id.SonyFlakeGenerator(),
)) ))
projections = append(projections, newToken(ctx, projections = append(projections, newToken(ctx,

View File

@ -2,12 +2,14 @@ package handler
import ( import (
"context" "context"
"slices"
"time" "time"
auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view" auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2" "github.com/zitadel/zitadel/internal/eventstore/handler/v2"
"github.com/zitadel/zitadel/internal/id"
query2 "github.com/zitadel/zitadel/internal/query" query2 "github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/org" "github.com/zitadel/zitadel/internal/repository/org"
@ -18,12 +20,15 @@ import (
const ( const (
userSessionTable = "auth.user_sessions" userSessionTable = "auth.user_sessions"
IDPrefixV1 = "V1_"
) )
type UserSession struct { type UserSession struct {
queries *query2.Queries queries *query2.Queries
view *auth_view.View view *auth_view.View
es handler.EventStore es handler.EventStore
idGenerator id.Generator
} }
var _ handler.Projection = (*UserSession)(nil) var _ handler.Projection = (*UserSession)(nil)
@ -33,14 +38,16 @@ func newUserSession(
config handler.Config, config handler.Config,
view *auth_view.View, view *auth_view.View,
queries *query2.Queries, queries *query2.Queries,
idGenerator id.Generator,
) *handler.Handler { ) *handler.Handler {
return handler.NewHandler( return handler.NewHandler(
ctx, ctx,
&config, &config,
&UserSession{ &UserSession{
queries: queries, queries: queries,
view: view, view: view,
es: config.Eventstore, 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) userAgent, err := agentIDFromSession(event)
if err != nil { if err != nil {
return nil, err return nil, err
@ -203,14 +210,34 @@ func sessionColumns(event eventstore.Event, columns ...handler.Column) ([]handle
}, columns...), nil }, 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) { 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 // in case anything needs to be change here check if appendEvent function needs the change as well
switch event.Type() { switch event.Type() {
case user.UserV1PasswordCheckSucceededType, case user.UserV1PasswordCheckSucceededType,
user.HumanPasswordCheckSucceededType: user.HumanPasswordCheckSucceededType:
columns, err := sessionColumns(event, columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()), handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
) )
if err != nil { if err != nil {
return nil, err 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 return handler.NewUpsertStatement(event, columns[0:3], columns), nil
case user.UserV1PasswordCheckFailedType, case user.UserV1PasswordCheckFailedType,
user.HumanPasswordCheckFailedType: user.HumanPasswordCheckFailedType:
columns, err := sessionColumns(event, columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}), handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
) )
if err != nil { if err != nil {
return nil, err 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 return handler.NewUpsertStatement(event, columns[0:3], columns), nil
case user.UserV1MFAOTPCheckSucceededType, case user.UserV1MFAOTPCheckSucceededType,
user.HumanMFAOTPCheckSucceededType: user.HumanMFAOTPCheckSucceededType:
columns, err := sessionColumns(event, columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, event.CreatedAt()), handler.NewCol(view_model.UserSessionKeySecondFactorVerification, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFATypeTOTP), handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFATypeTOTP),
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -240,9 +265,8 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
case user.UserV1MFAOTPCheckFailedType, case user.UserV1MFAOTPCheckFailedType,
user.HumanMFAOTPCheckFailedType, user.HumanMFAOTPCheckFailedType,
user.HumanU2FTokenCheckFailedType: user.HumanU2FTokenCheckFailedType:
columns, err := sessionColumns(event, columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, time.Time{}), handler.NewCol(view_model.UserSessionKeySecondFactorVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
) )
if err != nil { if err != nil {
return nil, err 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 return handler.NewUpsertStatement(event, columns[0:3], columns), nil
case user.UserV1SignedOutType, case user.UserV1SignedOutType,
user.HumanSignedOutType: user.HumanSignedOutType:
columns, err := sessionColumns(event, columns, err := u.sessionColumns(event,
handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}), handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}), handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, 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 { if err != nil {
return nil, err return nil, err
} }
columns, err := sessionColumns(event, columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeyExternalLoginVerification, event.CreatedAt()), handler.NewCol(view_model.UserSessionKeyExternalLoginVerification, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeySelectedIDPConfigID, data.SelectedIDPConfigID), handler.NewCol(view_model.UserSessionKeySelectedIDPConfigID, data.SelectedIDPConfigID),
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -285,10 +308,9 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
if err != nil { if err != nil {
return nil, err return nil, err
} }
columns, err := sessionColumns(event, columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeySecondFactorVerification, event.CreatedAt()), handler.NewCol(view_model.UserSessionKeySecondFactorVerification, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFATypeU2F), handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFATypeU2F),
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -300,11 +322,10 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
if err != nil { if err != nil {
return nil, err return nil, err
} }
columns, err := sessionColumns(event, columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, event.CreatedAt()), handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, event.CreatedAt()), handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, event.CreatedAt()),
handler.NewCol(view_model.UserSessionKeyMultiFactorVerificationType, domain.MFATypeU2FUserVerification), handler.NewCol(view_model.UserSessionKeyMultiFactorVerificationType, domain.MFATypeU2FUserVerification),
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -316,10 +337,9 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
if err != nil { if err != nil {
return nil, err return nil, err
} }
columns, err := sessionColumns(event, columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}), handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, time.Time{}), handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, time.Time{}),
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -429,8 +449,7 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
}, },
), nil ), nil
case user.HumanRegisteredType: case user.HumanRegisteredType:
columns, err := sessionColumns(event, columns, err := u.sessionColumnsActivate(event,
handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()), handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()),
) )
if err != nil { if err != nil {

View File

@ -12,12 +12,20 @@ const (
userSessionTable = "auth.user_sessions" userSessionTable = "auth.user_sessions"
) )
func (v *View) UserSessionByIDs(agentID, userID, instanceID string) (*model.UserSessionView, error) { func (v *View) UserSessionByIDs(ctx context.Context, agentID, userID, instanceID string) (*model.UserSessionView, error) {
return view.UserSessionByIDs(v.client, agentID, userID, instanceID) return view.UserSessionByIDs(ctx, v.client, agentID, userID, instanceID)
} }
func (v *View) UserSessionsByAgentID(agentID, instanceID string) ([]*model.UserSessionView, error) { func (v *View) UserSessionsByAgentID(ctx context.Context, agentID, instanceID string) ([]*model.UserSessionView, error) {
return view.UserSessionsByAgentID(v.client, agentID, instanceID) 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) { func (v *View) GetLatestUserSessionSequence(ctx context.Context, instanceID string) (_ *query.CurrentState, err error) {

View File

@ -6,4 +6,6 @@ import (
type UserRepository interface { type UserRepository interface {
UserSessionUserIDsByAgentID(ctx context.Context, agentID string) ([]string, error) 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)
} }

View File

@ -50,6 +50,7 @@ func (c *Commands) ApproveDeviceAuth(
authTime time.Time, authTime time.Time,
preferredLanguage *language.Tag, preferredLanguage *language.Tag,
userAgent *domain.UserAgent, userAgent *domain.UserAgent,
sessionID string,
) (*domain.ObjectDetails, error) { ) (*domain.ObjectDetails, error) {
model, err := c.getDeviceAuthWriteModelByDeviceCode(ctx, deviceCode) model, err := c.getDeviceAuthWriteModelByDeviceCode(ctx, deviceCode)
if err != nil { if err != nil {
@ -58,7 +59,7 @@ func (c *Commands) ApproveDeviceAuth(
if !model.State.Exists() { if !model.State.Exists() {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-Hief9", "Errors.DeviceAuth.NotFound") 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 { if err != nil {
return nil, err return nil, err
} }
@ -151,7 +152,7 @@ func (c *Commands) CreateOIDCSessionFromDeviceAuth(ctx context.Context, deviceCo
cmd.AddSession(ctx, cmd.AddSession(ctx,
deviceAuthModel.UserID, deviceAuthModel.UserID,
deviceAuthModel.UserOrgID, deviceAuthModel.UserOrgID,
"", deviceAuthModel.SessionID,
deviceAuthModel.ClientID, deviceAuthModel.ClientID,
deviceAuthModel.Audience, deviceAuthModel.Audience,
deviceAuthModel.Scopes, deviceAuthModel.Scopes,

View File

@ -28,6 +28,7 @@ type DeviceAuthWriteModel struct {
PreferredLanguage *language.Tag PreferredLanguage *language.Tag
UserAgent *domain.UserAgent UserAgent *domain.UserAgent
NeedRefreshToken bool NeedRefreshToken bool
SessionID string
} }
func NewDeviceAuthWriteModel(deviceCode, resourceOwner string) *DeviceAuthWriteModel { func NewDeviceAuthWriteModel(deviceCode, resourceOwner string) *DeviceAuthWriteModel {
@ -60,6 +61,7 @@ func (m *DeviceAuthWriteModel) Reduce() error {
m.AuthTime = e.AuthTime m.AuthTime = e.AuthTime
m.PreferredLanguage = e.PreferredLanguage m.PreferredLanguage = e.PreferredLanguage
m.UserAgent = e.UserAgent m.UserAgent = e.UserAgent
m.SessionID = e.SessionID
case *deviceauth.CanceledEvent: case *deviceauth.CanceledEvent:
m.State = e.Reason.State() m.State = e.Reason.State()
case *deviceauth.DoneEvent: case *deviceauth.DoneEvent:

View File

@ -137,6 +137,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
authTime time.Time authTime time.Time
preferredLanguage *language.Tag preferredLanguage *language.Tag
userAgent *domain.UserAgent userAgent *domain.UserAgent
sessionID string
} }
tests := []struct { tests := []struct {
name string name string
@ -161,6 +162,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
Description: gu.Ptr("firefox"), Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}}, Header: http.Header{"foo": []string{"bar"}},
}, },
"sessionID",
}, },
wantErr: zerrors.ThrowNotFound(nil, "COMMAND-Hief9", "Errors.DeviceAuth.NotFound"), wantErr: zerrors.ThrowNotFound(nil, "COMMAND-Hief9", "Errors.DeviceAuth.NotFound"),
}, },
@ -188,6 +190,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
Description: gu.Ptr("firefox"), Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}}, Header: http.Header{"foo": []string{"bar"}},
}, },
"sessionID",
), ),
), ),
), ),
@ -201,6 +204,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
Description: gu.Ptr("firefox"), Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}}, Header: http.Header{"foo": []string{"bar"}},
}, },
"sessionID",
}, },
wantErr: pushErr, wantErr: pushErr,
}, },
@ -228,6 +232,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
Description: gu.Ptr("firefox"), Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}}, Header: http.Header{"foo": []string{"bar"}},
}, },
"sessionID",
), ),
), ),
), ),
@ -241,6 +246,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
Description: gu.Ptr("firefox"), Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}}, Header: http.Header{"foo": []string{"bar"}},
}, },
"sessionID",
}, },
wantDetails: &domain.ObjectDetails{ wantDetails: &domain.ObjectDetails{
ResourceOwner: "instance1", ResourceOwner: "instance1",
@ -252,7 +258,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
c := &Commands{ c := &Commands{
eventstore: tt.fields.eventstore, 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) require.ErrorIs(t, err, tt.wantErr)
assertObjectDetails(t, tt.wantDetails, gotDetails) assertObjectDetails(t, tt.wantDetails, gotDetails)
}) })
@ -607,13 +613,14 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
Description: gu.Ptr("firefox"), Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}}, Header: http.Header{"foo": []string{"bar"}},
}, },
"sessionID",
), ),
), ),
), ),
expectFilter(), // token lifetime expectFilter(), // token lifetime
expectPush( expectPush(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate, 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{ []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow, "", &language.Afrikaans, &domain.UserAgent{
FingerprintID: gu.Ptr("fp1"), FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"), IP: net.ParseIP("1.2.3.4"),
@ -657,7 +664,8 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
Description: gu.Ptr("firefox"), Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}}, 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"), Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}}, Header: http.Header{"foo": []string{"bar"}},
}, },
"sessionID",
), ),
), ),
), ),
expectFilter(), // token lifetime expectFilter(), // token lifetime
expectPush( expectPush(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate, 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{ []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow, "", &language.Afrikaans, &domain.UserAgent{
FingerprintID: gu.Ptr("fp1"), FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"), IP: net.ParseIP("1.2.3.4"),
@ -742,6 +751,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
}, },
Reason: domain.TokenReasonAuthRequest, Reason: domain.TokenReasonAuthRequest,
RefreshToken: "VjJfb2lkY1Nlc3Npb25JRC1ydF9yZWZyZXNoVG9rZW5JRDp1c2VySUQ", //V2_oidcSessionID-rt_refreshTokenID:userID RefreshToken: "VjJfb2lkY1Nlc3Npb25JRC1ydF9yZWZyZXNoVG9rZW5JRDp1c2VySUQ", //V2_oidcSessionID-rt_refreshTokenID:userID
SessionID: "sessionID",
}, },
}, },
} }

View File

@ -25,6 +25,7 @@ type InstanceFeatures struct {
ImprovedPerformance []feature.ImprovedPerformanceType ImprovedPerformance []feature.ImprovedPerformanceType
WebKey *bool WebKey *bool
DebugOIDCParentError *bool DebugOIDCParentError *bool
OIDCSingleV1SessionTermination *bool
} }
func (m *InstanceFeatures) isEmpty() bool { func (m *InstanceFeatures) isEmpty() bool {
@ -37,7 +38,8 @@ func (m *InstanceFeatures) isEmpty() bool {
// nil check to allow unset improvements // nil check to allow unset improvements
m.ImprovedPerformance == nil && m.ImprovedPerformance == nil &&
m.WebKey == 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) { func (c *Commands) SetInstanceFeatures(ctx context.Context, f *InstanceFeatures) (*domain.ObjectDetails, error) {

View File

@ -69,6 +69,7 @@ func (m *InstanceFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder {
feature_v2.InstanceImprovedPerformanceEventType, feature_v2.InstanceImprovedPerformanceEventType,
feature_v2.InstanceWebKeyEventType, feature_v2.InstanceWebKeyEventType,
feature_v2.InstanceDebugOIDCParentErrorEventType, feature_v2.InstanceDebugOIDCParentErrorEventType,
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType,
). ).
Builder().ResourceOwner(m.ResourceOwner) Builder().ResourceOwner(m.ResourceOwner)
} }
@ -108,6 +109,9 @@ func reduceInstanceFeature(features *InstanceFeatures, key feature.Key, value an
case feature.KeyDebugOIDCParentError: case feature.KeyDebugOIDCParentError:
v := value.(bool) v := value.(bool)
features.DebugOIDCParentError = &v 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 = 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.WebKey, f.WebKey, feature_v2.InstanceWebKeyEventType)
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.DebugOIDCParentError, f.DebugOIDCParentError, feature_v2.InstanceDebugOIDCParentErrorEventType) 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 return cmds
} }

View File

@ -208,6 +208,10 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
ctx, aggregate, ctx, aggregate,
feature_v2.InstanceActionsEventType, true, feature_v2.InstanceActionsEventType, true,
), ),
feature_v2.NewSetEvent[bool](
ctx, aggregate,
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType, true,
),
), ),
), ),
args: args{ctx, &InstanceFeatures{ args: args{ctx, &InstanceFeatures{
@ -216,6 +220,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
LegacyIntrospection: gu.Ptr(true), LegacyIntrospection: gu.Ptr(true),
UserSchema: gu.Ptr(true), UserSchema: gu.Ptr(true),
Actions: gu.Ptr(true), Actions: gu.Ptr(true),
OIDCSingleV1SessionTermination: gu.Ptr(true),
}}, }},
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
ResourceOwner: "instance1", ResourceOwner: "instance1",
@ -246,6 +251,10 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
ctx, aggregate, ctx, aggregate,
feature_v2.InstanceLegacyIntrospectionEventType, true, feature_v2.InstanceLegacyIntrospectionEventType, true,
)), )),
feature_v2.NewSetEvent[bool](
context.Background(), aggregate,
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType, false,
),
), ),
expectPush( expectPush(
feature_v2.NewSetEvent[bool]( feature_v2.NewSetEvent[bool](
@ -262,6 +271,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
LoginDefaultOrg: gu.Ptr(true), LoginDefaultOrg: gu.Ptr(true),
TriggerIntrospectionProjections: gu.Ptr(false), TriggerIntrospectionProjections: gu.Ptr(false),
LegacyIntrospection: gu.Ptr(true), LegacyIntrospection: gu.Ptr(true),
OIDCSingleV1SessionTermination: gu.Ptr(false),
}}, }},
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
ResourceOwner: "instance1", ResourceOwner: "instance1",

View File

@ -136,6 +136,7 @@ func (c *Commands) CreateOIDCSession(ctx context.Context,
reason domain.TokenReason, reason domain.TokenReason,
actor *domain.TokenActor, actor *domain.TokenActor,
needRefreshToken bool, needRefreshToken bool,
sessionID string,
) (session *OIDCSession, err error) { ) (session *OIDCSession, err error) {
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()
@ -151,7 +152,7 @@ func (c *Commands) CreateOIDCSession(ctx context.Context,
cmd.UserImpersonated(ctx, userID, resourceOwner, clientID, actor) 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 { if err = cmd.AddAccessToken(ctx, scope, userID, resourceOwner, reason, actor); err != nil {
return nil, err return nil, err
} }

View File

@ -479,6 +479,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
reason domain.TokenReason reason domain.TokenReason
actor *domain.TokenActor actor *domain.TokenActor
needRefreshToken bool needRefreshToken bool
sessionID string
} }
tests := []struct { tests := []struct {
name string name string
@ -684,6 +685,89 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
RefreshToken: "VjJfb2lkY1Nlc3Npb25JRC1ydF9yZWZyZXNoVG9rZW5JRDp1c2VySUQ", //V2_oidcSessionID-rt_refreshTokenID:userID 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", name: "impersonation not allowed",
fields: fields{ fields: fields{
@ -839,6 +923,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
tt.args.reason, tt.args.reason,
tt.args.actor, tt.args.actor,
tt.args.needRefreshToken, tt.args.needRefreshToken,
tt.args.sessionID,
) )
require.ErrorIs(t, err, tt.wantErr) require.ErrorIs(t, err, tt.wantErr)
if got != nil { if got != nil {

View File

@ -17,6 +17,7 @@ type SystemFeatures struct {
UserSchema *bool UserSchema *bool
Actions *bool Actions *bool
ImprovedPerformance []feature.ImprovedPerformanceType ImprovedPerformance []feature.ImprovedPerformanceType
OIDCSingleV1SessionTermination *bool
} }
func (m *SystemFeatures) isEmpty() bool { func (m *SystemFeatures) isEmpty() bool {
@ -27,7 +28,8 @@ func (m *SystemFeatures) isEmpty() bool {
m.TokenExchange == nil && m.TokenExchange == nil &&
m.Actions == nil && m.Actions == nil &&
// nil check to allow unset improvements // 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) { func (c *Commands) SetSystemFeatures(ctx context.Context, f *SystemFeatures) (*domain.ObjectDetails, error) {

View File

@ -60,6 +60,7 @@ func (m *SystemFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder {
feature_v2.SystemTokenExchangeEventType, feature_v2.SystemTokenExchangeEventType,
feature_v2.SystemActionsEventType, feature_v2.SystemActionsEventType,
feature_v2.SystemImprovedPerformanceEventType, feature_v2.SystemImprovedPerformanceEventType,
feature_v2.SystemOIDCSingleV1SessionTerminationEventType,
). ).
Builder().ResourceOwner(m.ResourceOwner) Builder().ResourceOwner(m.ResourceOwner)
} }
@ -92,6 +93,9 @@ func reduceSystemFeature(features *SystemFeatures, key feature.Key, value any) {
features.Actions = &v features.Actions = &v
case feature.KeyImprovedPerformance: case feature.KeyImprovedPerformance:
features.ImprovedPerformance = value.([]feature.ImprovedPerformanceType) 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.TokenExchange, f.TokenExchange, feature_v2.SystemTokenExchangeEventType)
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.Actions, f.Actions, feature_v2.SystemActionsEventType) 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 = 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 return cmds
} }

View File

@ -176,6 +176,10 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
context.Background(), aggregate, context.Background(), aggregate,
feature_v2.SystemActionsEventType, true, feature_v2.SystemActionsEventType, true,
), ),
feature_v2.NewSetEvent[bool](
context.Background(), aggregate,
feature_v2.SystemOIDCSingleV1SessionTerminationEventType, true,
),
), ),
), ),
args: args{context.Background(), &SystemFeatures{ args: args{context.Background(), &SystemFeatures{
@ -184,6 +188,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
LegacyIntrospection: gu.Ptr(true), LegacyIntrospection: gu.Ptr(true),
UserSchema: gu.Ptr(true), UserSchema: gu.Ptr(true),
Actions: gu.Ptr(true), Actions: gu.Ptr(true),
OIDCSingleV1SessionTermination: gu.Ptr(true),
}}, }},
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
ResourceOwner: "SYSTEM", ResourceOwner: "SYSTEM",
@ -232,6 +237,10 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
context.Background(), aggregate, context.Background(), aggregate,
feature_v2.SystemActionsEventType, false, feature_v2.SystemActionsEventType, false,
), ),
feature_v2.NewSetEvent[bool](
context.Background(), aggregate,
feature_v2.SystemOIDCSingleV1SessionTerminationEventType, false,
),
), ),
), ),
args: args{context.Background(), &SystemFeatures{ args: args{context.Background(), &SystemFeatures{
@ -240,6 +249,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
LegacyIntrospection: gu.Ptr(true), LegacyIntrospection: gu.Ptr(true),
UserSchema: gu.Ptr(true), UserSchema: gu.Ptr(true),
Actions: gu.Ptr(false), Actions: gu.Ptr(false),
OIDCSingleV1SessionTermination: gu.Ptr(false),
}}, }},
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
ResourceOwner: "SYSTEM", ResourceOwner: "SYSTEM",

View File

@ -62,6 +62,8 @@ type AuthRequest struct {
SAMLRequestID string SAMLRequestID string
// orgID the policies were last loaded with // orgID the policies were last loaded with
policyOrgID string policyOrgID string
// SessionID is set to the computed sessionID of the login session table
SessionID string
} }
func (a *AuthRequest) SetPolicyOrgID(id string) { func (a *AuthRequest) SetPolicyOrgID(id string) {

View File

@ -146,18 +146,17 @@ func NewUpsertStatement(event eventstore.Event, conflictCols []Column, values []
conflictTarget[i] = col.Name conflictTarget[i] = col.Name
} }
config := execConfig{ config := execConfig{}
args: args,
}
if len(values) == 0 { if len(values) == 0 {
config.err = ErrNoValues config.err = ErrNoValues
} }
updateCols, updateVals := getUpdateCols(values, conflictTarget) updateCols, updateVals, args := getUpdateCols(values, conflictTarget, params, args)
if len(updateCols) == 0 || len(updateVals) == 0 { if len(updateCols) == 0 || len(updateVals) == 0 {
config.err = ErrNoValues config.err = ErrNoValues
} }
config.args = args
q := func(config execConfig) string { q := func(config execConfig) string {
var updateStmt 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)) updateCols = make([]string, len(cols))
updateVals = make([]string, len(cols)) updateVals = make([]string, len(cols))
updatedArgs = args
for i := len(cols) - 1; i >= 0; i-- { for i := len(cols) - 1; i >= 0; i-- {
col := cols[i] col := cols[i]
table := "EXCLUDED"
if onlyOnInsert, ok := col.Value.(*onlySetValueOnInsert); ok {
table = onlyOnInsert.Table
}
updateCols[i] = col.Name 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 { for _, conflict := range conflictTarget {
if conflict == col.Name { if conflict == col.Name {
copy(updateCols[i:], updateCols[i+1:]) 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 { func NewUpdateStatement(event eventstore.Event, values []Column, conditions []Condition, opts ...execOption) *Statement {

View File

@ -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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@ -16,6 +16,7 @@ const (
KeyImprovedPerformance KeyImprovedPerformance
KeyWebKey KeyWebKey
KeyDebugOIDCParentError KeyDebugOIDCParentError
KeyOIDCSingleV1SessionTermination
) )
//go:generate enumer -type Level -transform snake -trimprefix Level //go:generate enumer -type Level -transform snake -trimprefix Level
@ -41,6 +42,7 @@ type Features struct {
ImprovedPerformance []ImprovedPerformanceType `json:"improved_performance,omitempty"` ImprovedPerformance []ImprovedPerformanceType `json:"improved_performance,omitempty"`
WebKey bool `json:"web_key,omitempty"` WebKey bool `json:"web_key,omitempty"`
DebugOIDCParentError bool `json:"debug_oidc_parent_error,omitempty"` DebugOIDCParentError bool `json:"debug_oidc_parent_error,omitempty"`
OIDCSingleV1SessionTermination bool `json:"terminate_single_v1_session,omitempty"`
} }
type ImprovedPerformanceType int32 type ImprovedPerformanceType int32

View File

@ -7,11 +7,11 @@ import (
"strings" "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 { func (i Key) String() string {
if i < 0 || i >= Key(len(_KeyIndex)-1) { if i < 0 || i >= Key(len(_KeyIndex)-1) {
@ -34,9 +34,10 @@ func _KeyNoOp() {
_ = x[KeyImprovedPerformance-(7)] _ = x[KeyImprovedPerformance-(7)]
_ = x[KeyWebKey-(8)] _ = x[KeyWebKey-(8)]
_ = x[KeyDebugOIDCParentError-(9)] _ = 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{ var _KeyNameToValueMap = map[string]Key{
_KeyName[0:11]: KeyUnspecified, _KeyName[0:11]: KeyUnspecified,
@ -59,6 +60,8 @@ var _KeyNameToValueMap = map[string]Key{
_KeyLowerName[133:140]: KeyWebKey, _KeyLowerName[133:140]: KeyWebKey,
_KeyName[140:163]: KeyDebugOIDCParentError, _KeyName[140:163]: KeyDebugOIDCParentError,
_KeyLowerName[140:163]: KeyDebugOIDCParentError, _KeyLowerName[140:163]: KeyDebugOIDCParentError,
_KeyName[163:190]: KeyOIDCSingleV1SessionTermination,
_KeyLowerName[163:190]: KeyOIDCSingleV1SessionTermination,
} }
var _KeyNames = []string{ var _KeyNames = []string{
@ -72,6 +75,7 @@ var _KeyNames = []string{
_KeyName[113:133], _KeyName[113:133],
_KeyName[133:140], _KeyName[133:140],
_KeyName[140:163], _KeyName[140:163],
_KeyName[163:190],
} }
// KeyString retrieves an enum value from the enum constants string name. // KeyString retrieves an enum value from the enum constants string name.

View 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

View File

@ -18,6 +18,7 @@ type InstanceFeatures struct {
ImprovedPerformance FeatureSource[[]feature.ImprovedPerformanceType] ImprovedPerformance FeatureSource[[]feature.ImprovedPerformanceType]
WebKey FeatureSource[bool] WebKey FeatureSource[bool]
DebugOIDCParentError FeatureSource[bool] DebugOIDCParentError FeatureSource[bool]
OIDCSingleV1SessionTermination FeatureSource[bool]
} }
func (q *Queries) GetInstanceFeatures(ctx context.Context, cascade bool) (_ *InstanceFeatures, err error) { func (q *Queries) GetInstanceFeatures(ctx context.Context, cascade bool) (_ *InstanceFeatures, err error) {

View File

@ -69,6 +69,7 @@ func (m *InstanceFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
feature_v2.InstanceImprovedPerformanceEventType, feature_v2.InstanceImprovedPerformanceEventType,
feature_v2.InstanceWebKeyEventType, feature_v2.InstanceWebKeyEventType,
feature_v2.InstanceDebugOIDCParentErrorEventType, feature_v2.InstanceDebugOIDCParentErrorEventType,
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType,
). ).
Builder().ResourceOwner(m.ResourceOwner) Builder().ResourceOwner(m.ResourceOwner)
} }
@ -92,6 +93,7 @@ func (m *InstanceFeaturesReadModel) populateFromSystem() bool {
m.instance.TokenExchange = m.system.TokenExchange m.instance.TokenExchange = m.system.TokenExchange
m.instance.Actions = m.system.Actions m.instance.Actions = m.system.Actions
m.instance.ImprovedPerformance = m.system.ImprovedPerformance m.instance.ImprovedPerformance = m.system.ImprovedPerformance
m.instance.OIDCSingleV1SessionTermination = m.system.OIDCSingleV1SessionTermination
return true return true
} }
@ -121,6 +123,8 @@ func reduceInstanceFeatureSet[T any](features *InstanceFeatures, event *feature_
features.WebKey.set(level, event.Value) features.WebKey.set(level, event.Value)
case feature.KeyDebugOIDCParentError: case feature.KeyDebugOIDCParentError:
features.DebugOIDCParentError.set(level, event.Value) features.DebugOIDCParentError.set(level, event.Value)
case feature.KeyOIDCSingleV1SessionTermination:
features.OIDCSingleV1SessionTermination.set(level, event.Value)
} }
return nil return nil
} }

View File

@ -96,6 +96,10 @@ func (*instanceFeatureProjection) Reducers() []handler.AggregateReducer {
Event: feature_v2.InstanceDebugOIDCParentErrorEventType, Event: feature_v2.InstanceDebugOIDCParentErrorEventType,
Reduce: reduceInstanceSetFeature[bool], Reduce: reduceInstanceSetFeature[bool],
}, },
{
Event: feature_v2.InstanceOIDCSingleV1SessionTerminationEventType,
Reduce: reduceInstanceSetFeature[bool],
},
{ {
Event: instance.InstanceRemovedEventType, Event: instance.InstanceRemovedEventType,
Reduce: reduceInstanceRemovedHelper(InstanceDomainInstanceIDCol), Reduce: reduceInstanceRemovedHelper(InstanceDomainInstanceIDCol),

View File

@ -27,6 +27,7 @@ type SystemFeatures struct {
TokenExchange FeatureSource[bool] TokenExchange FeatureSource[bool]
Actions FeatureSource[bool] Actions FeatureSource[bool]
ImprovedPerformance FeatureSource[[]feature.ImprovedPerformanceType] ImprovedPerformance FeatureSource[[]feature.ImprovedPerformanceType]
OIDCSingleV1SessionTermination FeatureSource[bool]
} }
func (q *Queries) GetSystemFeatures(ctx context.Context) (_ *SystemFeatures, err error) { func (q *Queries) GetSystemFeatures(ctx context.Context) (_ *SystemFeatures, err error) {

View File

@ -57,6 +57,7 @@ func (m *SystemFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
feature_v2.SystemTokenExchangeEventType, feature_v2.SystemTokenExchangeEventType,
feature_v2.SystemActionsEventType, feature_v2.SystemActionsEventType,
feature_v2.SystemImprovedPerformanceEventType, feature_v2.SystemImprovedPerformanceEventType,
feature_v2.SystemOIDCSingleV1SessionTerminationEventType,
). ).
Builder().ResourceOwner(m.ResourceOwner) Builder().ResourceOwner(m.ResourceOwner)
} }
@ -88,6 +89,8 @@ func reduceSystemFeatureSet[T any](features *SystemFeatures, event *feature_v2.S
features.Actions.set(level, event.Value) features.Actions.set(level, event.Value)
case feature.KeyImprovedPerformance: case feature.KeyImprovedPerformance:
features.ImprovedPerformance.set(level, event.Value) features.ImprovedPerformance.set(level, event.Value)
case feature.KeyOIDCSingleV1SessionTermination:
features.OIDCSingleV1SessionTermination.set(level, event.Value)
} }
return nil return nil
} }

View File

@ -72,6 +72,7 @@ type ApprovedEvent struct {
AuthTime time.Time AuthTime time.Time
PreferredLanguage *language.Tag PreferredLanguage *language.Tag
UserAgent *domain.UserAgent UserAgent *domain.UserAgent
SessionID string
} }
func (e *ApprovedEvent) SetBaseEvent(b *eventstore.BaseEvent) { func (e *ApprovedEvent) SetBaseEvent(b *eventstore.BaseEvent) {
@ -95,17 +96,19 @@ func NewApprovedEvent(
authTime time.Time, authTime time.Time,
preferredLanguage *language.Tag, preferredLanguage *language.Tag,
userAgent *domain.UserAgent, userAgent *domain.UserAgent,
sessionID string,
) *ApprovedEvent { ) *ApprovedEvent {
return &ApprovedEvent{ return &ApprovedEvent{
eventstore.NewBaseEventForPush( BaseEvent: eventstore.NewBaseEventForPush(
ctx, aggregate, ApprovedEventType, ctx, aggregate, ApprovedEventType,
), ),
userID, UserID: userID,
userOrgID, UserOrgID: userOrgID,
userAuthMethods, UserAuthMethods: userAuthMethods,
authTime, AuthTime: authTime,
preferredLanguage, PreferredLanguage: preferredLanguage,
userAgent, UserAgent: userAgent,
SessionID: sessionID,
} }
} }

View File

@ -14,6 +14,7 @@ func init() {
eventstore.RegisterFilterEventMapper(AggregateType, SystemTokenExchangeEventType, eventstore.GenericEventMapper[SetEvent[bool]]) eventstore.RegisterFilterEventMapper(AggregateType, SystemTokenExchangeEventType, eventstore.GenericEventMapper[SetEvent[bool]])
eventstore.RegisterFilterEventMapper(AggregateType, SystemActionsEventType, eventstore.GenericEventMapper[SetEvent[bool]]) eventstore.RegisterFilterEventMapper(AggregateType, SystemActionsEventType, eventstore.GenericEventMapper[SetEvent[bool]])
eventstore.RegisterFilterEventMapper(AggregateType, SystemImprovedPerformanceEventType, eventstore.GenericEventMapper[SetEvent[[]feature.ImprovedPerformanceType]]) 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, InstanceResetEventType, eventstore.GenericEventMapper[ResetEvent])
eventstore.RegisterFilterEventMapper(AggregateType, InstanceLoginDefaultOrgEventType, eventstore.GenericEventMapper[SetEvent[bool]]) 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, InstanceImprovedPerformanceEventType, eventstore.GenericEventMapper[SetEvent[[]feature.ImprovedPerformanceType]])
eventstore.RegisterFilterEventMapper(AggregateType, InstanceWebKeyEventType, eventstore.GenericEventMapper[SetEvent[bool]]) eventstore.RegisterFilterEventMapper(AggregateType, InstanceWebKeyEventType, eventstore.GenericEventMapper[SetEvent[bool]])
eventstore.RegisterFilterEventMapper(AggregateType, InstanceDebugOIDCParentErrorEventType, eventstore.GenericEventMapper[SetEvent[bool]]) eventstore.RegisterFilterEventMapper(AggregateType, InstanceDebugOIDCParentErrorEventType, eventstore.GenericEventMapper[SetEvent[bool]])
eventstore.RegisterFilterEventMapper(AggregateType, InstanceOIDCSingleV1SessionTerminationEventType, eventstore.GenericEventMapper[SetEvent[bool]])
} }

View File

@ -19,6 +19,7 @@ var (
SystemTokenExchangeEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyTokenExchange) SystemTokenExchangeEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyTokenExchange)
SystemActionsEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyActions) SystemActionsEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyActions)
SystemImprovedPerformanceEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyImprovedPerformance) SystemImprovedPerformanceEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyImprovedPerformance)
SystemOIDCSingleV1SessionTerminationEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyOIDCSingleV1SessionTermination)
InstanceResetEventType = resetEventTypeFromFeature(feature.LevelInstance) InstanceResetEventType = resetEventTypeFromFeature(feature.LevelInstance)
InstanceLoginDefaultOrgEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyLoginDefaultOrg) InstanceLoginDefaultOrgEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyLoginDefaultOrg)
@ -30,6 +31,7 @@ var (
InstanceImprovedPerformanceEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyImprovedPerformance) InstanceImprovedPerformanceEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyImprovedPerformance)
InstanceWebKeyEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyWebKey) InstanceWebKeyEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyWebKey)
InstanceDebugOIDCParentErrorEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyDebugOIDCParentError) InstanceDebugOIDCParentErrorEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyDebugOIDCParentError)
InstanceOIDCSingleV1SessionTerminationEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyOIDCSingleV1SessionTermination)
) )
const ( const (

1365
internal/static/i18n/id.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -27,6 +27,7 @@ type UserSessionView struct {
MultiFactorVerification time.Time MultiFactorVerification time.Time
MultiFactorVerificationType domain.MFAType MultiFactorVerificationType domain.MFAType
Sequence uint64 Sequence uint64
ID string
} }
type UserSessionSearchRequest struct { type UserSessionSearchRequest struct {

View File

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

View File

@ -32,6 +32,7 @@ const (
UserSessionKeyPasswordlessVerification = "passwordless_verification" UserSessionKeyPasswordlessVerification = "passwordless_verification"
UserSessionKeyExternalLoginVerification = "external_login_verification" UserSessionKeyExternalLoginVerification = "external_login_verification"
UserSessionKeySelectedIDPConfigID = "selected_idp_config_id" UserSessionKeySelectedIDPConfigID = "selected_idp_config_id"
UserSessionKeyID = "id"
) )
type UserSessionView struct { type UserSessionView struct {
@ -59,6 +60,12 @@ type UserSessionView struct {
MultiFactorVerificationType sql.NullInt32 `json:"-" gorm:"column:multi_factor_verification_type"` MultiFactorVerificationType sql.NullInt32 `json:"-" gorm:"column:multi_factor_verification_type"`
Sequence uint64 `json:"-" gorm:"column:sequence"` Sequence uint64 `json:"-" gorm:"column:sequence"`
InstanceID string `json:"instanceID" gorm:"column:instance_id;primary_key"` 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 { type userAgentIDPayload struct {
@ -95,6 +102,7 @@ func UserSessionToModel(userSession *UserSessionView) *model.UserSessionView {
MultiFactorVerification: userSession.MultiFactorVerification.Time, MultiFactorVerification: userSession.MultiFactorVerification.Time,
MultiFactorVerificationType: domain.MFAType(userSession.MultiFactorVerificationType.Int32), MultiFactorVerificationType: domain.MFAType(userSession.MultiFactorVerificationType.Int32),
Sequence: userSession.Sequence, Sequence: userSession.Sequence,
ID: userSession.ID.String,
} }
} }

View File

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

View File

@ -17,7 +17,8 @@ SELECT s.creation_date,
s.multi_factor_verification, s.multi_factor_verification,
s.multi_factor_verification_type, s.multi_factor_verification_type,
s.sequence, s.sequence,
s.instance_id s.instance_id,
s.id
FROM auth.user_sessions s 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 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 LEFT JOIN projections.users13_humans h ON s.user_id = h.user_id AND s.instance_id = h.instance_id

View File

@ -1,6 +1,7 @@
package view package view
import ( import (
"context"
"database/sql" "database/sql"
_ "embed" _ "embed"
"errors" "errors"
@ -16,8 +17,15 @@ var userSessionByIDQuery string
//go:embed user_sessions_by_user_agent.sql //go:embed user_sessions_by_user_agent.sql
var userSessionsByUserAgentQuery string var userSessionsByUserAgentQuery string
func UserSessionByIDs(db *database.DB, agentID, userID, instanceID string) (userSession *model.UserSessionView, err error) { //go:embed user_agent_by_user_session_id.sql
err = db.QueryRow( 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 { func(row *sql.Row) error {
userSession, err = scanUserSession(row) userSession, err = scanUserSession(row)
return err return err
@ -29,8 +37,10 @@ func UserSessionByIDs(db *database.DB, agentID, userID, instanceID string) (user
) )
return userSession, err 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 { func(rows *sql.Rows) error {
userSessions, err = scanUserSessions(rows) userSessions, err = scanUserSessions(rows)
return err return err
@ -42,6 +52,51 @@ func UserSessionsByAgentID(db *database.DB, agentID, instanceID string) (userSes
return userSessions, err 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) { func scanUserSession(row *sql.Row) (*model.UserSessionView, error) {
session := new(model.UserSessionView) session := new(model.UserSessionView)
err := row.Scan( err := row.Scan(
@ -65,6 +120,7 @@ func scanUserSession(row *sql.Row) (*model.UserSessionView, error) {
&session.MultiFactorVerificationType, &session.MultiFactorVerificationType,
&session.Sequence, &session.Sequence,
&session.InstanceID, &session.InstanceID,
&session.ID,
) )
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return nil, zerrors.ThrowNotFound(nil, "VIEW-NGBs1", "Errors.UserSession.NotFound") 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.MultiFactorVerificationType,
&session.Sequence, &session.Sequence,
&session.InstanceID, &session.InstanceID,
&session.ID,
) )
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -17,7 +17,8 @@ SELECT s.creation_date,
s.multi_factor_verification, s.multi_factor_verification,
s.multi_factor_verification_type, s.multi_factor_verification_type,
s.sequence, s.sequence,
s.instance_id s.instance_id,
s.id
FROM auth.user_sessions s 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 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 LEFT JOIN projections.users13_humans h ON s.user_id = h.user_id AND s.instance_id = h.instance_id

View File

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

View File

@ -61,6 +61,13 @@ message SetSystemFeaturesRequest{
description: "Improves performance of specified execution paths."; 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 { message SetSystemFeaturesResponse {
@ -125,4 +132,11 @@ message GetSystemFeaturesResponse {
description: "Improves performance of specified execution paths."; 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.";
}
];
} }

View File

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

View File

@ -61,6 +61,13 @@ message SetSystemFeaturesRequest{
description: "Improves performance of specified execution paths."; 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 { message SetSystemFeaturesResponse {
@ -125,4 +132,11 @@ message GetSystemFeaturesResponse {
description: "Improves performance of specified execution paths."; 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