Merge branch 'main' into next

This commit is contained in:
Livio Spring 2024-04-29 07:54:05 +02:00
commit 6e60335789
No known key found for this signature in database
GPG Key ID: 26BB1C2FA5952CF0
149 changed files with 14706 additions and 2131 deletions

View File

@ -52,6 +52,8 @@ jobs:
go_version: "1.22" go_version: "1.22"
core_cache_key: ${{ needs.core.outputs.cache_key }} core_cache_key: ${{ needs.core.outputs.cache_key }}
core_cache_path: ${{ needs.core.outputs.cache_path }} core_cache_path: ${{ needs.core.outputs.cache_path }}
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
lint: lint:
needs: [core, console] needs: [core, console]

View File

@ -12,6 +12,9 @@ on:
core_cache_path: core_cache_path:
required: true required: true
type: string type: string
secrets:
CODECOV_TOKEN:
required: true
jobs: jobs:
postgres: postgres:

4
.gitignore vendored
View File

@ -82,3 +82,7 @@ go.work
go.work.sum go.work.sum
# Local Netlify folder # Local Netlify folder
.netlify .netlify
load-test/node_modules
load-test/yarn-error.log
load-test/dist

View File

@ -96,7 +96,7 @@ Yet it offers everything you need for a customer identity ([CIAM](https://zitade
- [API-first approach](https://zitadel.com/docs/apis/introduction) - [API-first approach](https://zitadel.com/docs/apis/introduction)
- [Multi-tenancy](https://zitadel.com/docs/guides/solution-scenarios/b2b) authentication and access management - [Multi-tenancy](https://zitadel.com/docs/guides/solution-scenarios/b2b) authentication and access management
- Strong audit trail thanks to [event sourcing](https://zitadel.com/docs/concepts/eventstore/overview) as storage pattern - [Strong audit trail](https://zitadel.com/docs/concepts/features/audit-trail) thanks to [event sourcing](https://zitadel.com/docs/concepts/eventstore/overview) as storage pattern
- [Actions](https://zitadel.com/docs/apis/actions/introduction) to react on events with custom code and extended ZITADEL for you needs - [Actions](https://zitadel.com/docs/apis/actions/introduction) to react on events with custom code and extended ZITADEL for you needs
- [Branding](https://zitadel.com/docs/guides/manage/customize/branding) for a uniform user experience across multiple organizations - [Branding](https://zitadel.com/docs/guides/manage/customize/branding) for a uniform user experience across multiple organizations
- [Self-service](https://zitadel.com/docs/concepts/features/selfservice) for end-users, business customers, and administrators - [Self-service](https://zitadel.com/docs/concepts/features/selfservice) for end-users, business customers, and administrators
@ -107,16 +107,17 @@ Yet it offers everything you need for a customer identity ([CIAM](https://zitade
Authentication Authentication
- Single Sign On (SSO) - Single Sign On (SSO)
- Passkeys support (FIDO2 / WebAuthN) - [Passkeys support (FIDO2 / WebAuthN)](https://zitadel.com/docs/concepts/features/passkeys)
- Username / Password - Username / Password
- Multifactor authentication with OTP, U2F, Email OTP, SMS OTP - Multifactor authentication with OTP, U2F, Email OTP, SMS OTP
- LDAP - [LDAP](https://zitadel.com/docs/guides/integrate/identity-providers/ldap)
- External enterprise identity providers and social logins - [External enterprise identity providers and social logins](https://zitadel.com/docs/guides/integrate/identity-providers/introduction)
- [Device authorization](https://zitadel.com/docs/guides/solution-scenarios/device-authorization) - [Device authorization](https://zitadel.com/docs/guides/solution-scenarios/device-authorization)
- [OpenID Connect certified](https://openid.net/certification/#OPs) => [OIDC Endpoints](https://zitadel.com/docs/apis/openidoauth/endpoints) - [OpenID Connect certified](https://openid.net/certification/#OPs) => [OIDC Endpoints](https://zitadel.com/docs/apis/openidoauth/endpoints)
- [SAML 2.0](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) => [SAML Endpoints](https://zitadel.com/docs/apis/saml/endpoints) - [SAML 2.0](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) => [SAML Endpoints](https://zitadel.com/docs/apis/saml/endpoints)
- [Custom sessions](https://zitadel.com/docs/guides/integrate/login-ui/username-password) if you need to go beyond OIDC or SAML - [Custom sessions](https://zitadel.com/docs/guides/integrate/login-ui/username-password) if you need to go beyond OIDC or SAML
- [Machine-to-machine](https://zitadel.com/docs/guides/integrate/serviceusers) with JWT profile, Personal Access Tokens (PAT), and Client Credentials - [Machine-to-machine](https://zitadel.com/docs/guides/integrate/service-users/authenticate-service-users) with JWT profile, Personal Access Tokens (PAT), and Client Credentials
- [Token exchange and impersonation](https://zitadel.com/docs/guides/integrate/token-exchange)
Multi-Tenancy Multi-Tenancy
@ -130,6 +131,10 @@ Integration
- [GRPC and REST APIs](https://zitadel.com/docs/apis/introduction) for every functionality and resource - [GRPC and REST APIs](https://zitadel.com/docs/apis/introduction) for every functionality and resource
- [Actions](https://zitadel.com/docs/apis/actions/introduction) to call any API, send webhooks, adjust workflows, or customize tokens - [Actions](https://zitadel.com/docs/apis/actions/introduction) to call any API, send webhooks, adjust workflows, or customize tokens
- [Role Based Access Control (RBAC)](https://zitadel.com/docs/guides/integrate/retrieve-user-roles) - [Role Based Access Control (RBAC)](https://zitadel.com/docs/guides/integrate/retrieve-user-roles)
- [Examples and SDKs](https://zitadel.com/docs/sdk-examples/introduction)
- [Audit Log and SOC/SIEM](https://zitadel.com/docs/guides/integrate/external-audit-log)
- [User registration and onboarding](https://zitadel.com/docs/guides/integrate/onboarding)
- [Hosted and custom login user interface](https://zitadel.com/docs/guides/integrate/login-ui)
Self-Service Self-Service
- [Self-registration](https://zitadel.com/docs/concepts/features/selfservice#registration) including verification - [Self-registration](https://zitadel.com/docs/concepts/features/selfservice#registration) including verification

View File

@ -61,7 +61,7 @@
<ng-container matColumnDef="expirationDate"> <ng-container matColumnDef="expirationDate">
<th mat-header-cell *matHeaderCellDef>{{ 'USER.MACHINE.EXPIRATIONDATE' | translate }}</th> <th mat-header-cell *matHeaderCellDef>{{ 'USER.MACHINE.EXPIRATIONDATE' | translate }}</th>
<td mat-cell *matCellDef="let key"> <td mat-cell *matCellDef="let key">
{{ key.expirationDate | timestampToDate | localizedDate: 'EEE dd. MMM YYYY, HH:mm' }} {{ key.expirationDate | timestampToDate | localizedDate: 'EEE dd. MMM yyyy, HH:mm' }}
</td> </td>
</ng-container> </ng-container>

View File

@ -4,7 +4,7 @@
<span>{{ length }} </span>{{ 'PAGINATOR.COUNT' | translate }} <span>{{ length }} </span>{{ 'PAGINATOR.COUNT' | translate }}
</p> </p>
<p class="ts cnsl-secondary-text" *ngIf="timestamp" data-e2e="timestamp"> <p class="ts cnsl-secondary-text" *ngIf="timestamp" data-e2e="timestamp">
{{ timestamp | timestampToDate | localizedDate: 'EEEE dd. MMM YYYY, HH:mm' }} {{ timestamp | timestampToDate | localizedDate: 'EEEE dd. MMM yyyy, HH:mm' }}
</p> </p>
</div> </div>
<span class="fill-space"></span> <span class="fill-space"></span>

View File

@ -56,7 +56,7 @@
<ng-container matColumnDef="expirationDate"> <ng-container matColumnDef="expirationDate">
<th mat-header-cell *matHeaderCellDef>{{ 'USER.MACHINE.EXPIRATIONDATE' | translate }}</th> <th mat-header-cell *matHeaderCellDef>{{ 'USER.MACHINE.EXPIRATIONDATE' | translate }}</th>
<td mat-cell *matCellDef="let key"> <td mat-cell *matCellDef="let key">
{{ key.expirationDate | timestampToDate | localizedDate: 'EEE dd. MMM YYYY, HH:mm' }} {{ key.expirationDate | timestampToDate | localizedDate: 'EEE dd. MMM yyyy, HH:mm' }}
</td> </td>
</ng-container> </ng-container>

View File

@ -13,14 +13,14 @@
<div class="row"> <div class="row">
<p class="left cnsl-secondary-text">{{ 'USER.MACHINE.CREATIONDATE' | translate }}</p> <p class="left cnsl-secondary-text">{{ 'USER.MACHINE.CREATIONDATE' | translate }}</p>
<p *ngIf="keyResponse.details && keyResponse.details.creationDate" class="right"> <p *ngIf="keyResponse.details && keyResponse.details.creationDate" class="right">
{{ keyResponse.details.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM YYYY, HH:mm' }} {{ keyResponse.details.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM yyyy, HH:mm' }}
</p> </p>
</div> </div>
<div class="row" *ngIf="expirationDate"> <div class="row" *ngIf="expirationDate">
<p class="left cnsl-secondary-text">{{ 'USER.MACHINE.EXPIRATIONDATE' | translate }}</p> <p class="left cnsl-secondary-text">{{ 'USER.MACHINE.EXPIRATIONDATE' | translate }}</p>
<p class="right"> <p class="right">
{{ expirationDate | localizedDate: 'EEE dd. MMM YYYY, HH:mm' }} {{ expirationDate | localizedDate: 'EEE dd. MMM yyyy, HH:mm' }}
</p> </p>
</div> </div>

View File

@ -15,6 +15,7 @@ import { Member } from 'src/app/proto/generated/zitadel/member_pb';
import { Metadata } from 'src/app/proto/generated/zitadel/metadata_pb'; import { Metadata } from 'src/app/proto/generated/zitadel/metadata_pb';
import { Org, OrgState } from 'src/app/proto/generated/zitadel/org_pb'; import { Org, OrgState } from 'src/app/proto/generated/zitadel/org_pb';
import { User } from 'src/app/proto/generated/zitadel/user_pb'; import { User } from 'src/app/proto/generated/zitadel/user_pb';
import { AdminService } from 'src/app/services/admin.service';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service'; import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ManagementService } from 'src/app/services/mgmt.service'; import { ManagementService } from 'src/app/services/mgmt.service';
@ -48,6 +49,7 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
private auth: GrpcAuthService, private auth: GrpcAuthService,
private dialog: MatDialog, private dialog: MatDialog,
public mgmtService: ManagementService, public mgmtService: ManagementService,
private adminService: AdminService,
private toast: ToastService, private toast: ToastService,
private router: Router, private router: Router,
breadcrumbService: BreadcrumbService, breadcrumbService: BreadcrumbService,
@ -146,15 +148,34 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
width: '400px', width: '400px',
}); });
// Before we remove the org we get the current default org
// we have to query before the current org is removed
dialogRef.afterClosed().subscribe((resp) => { dialogRef.afterClosed().subscribe((resp) => {
if (resp) { if (resp) {
this.mgmtService this.adminService
.removeOrg() .getDefaultOrg()
.then(() => { .then((response) => {
setTimeout(() => { const org = response?.org;
this.router.navigate(['/orgs']); if (org) {
}, 1000); // We now remove the org
this.toast.showInfo('ORG.TOAST.DELETED', true); this.mgmtService
.removeOrg()
.then(() => {
setTimeout(() => {
// We change active org to default org as
// current org was deleted to avoid Organization doesn't exist
this.auth.setActiveOrg(org);
// Now we visit orgs
this.router.navigate(['/orgs']);
}, 1000);
this.toast.showInfo('ORG.TOAST.DELETED', true);
})
.catch((error) => {
this.toast.showError(error);
});
} else {
this.toast.showError('ORG.TOAST.DEFAULTORGNOTFOUND', false, true);
}
}) })
.catch((error) => { .catch((error) => {
this.toast.showError(error); this.toast.showError(error);

View File

@ -90,6 +90,8 @@ import {
GetDefaultLanguageResponse, GetDefaultLanguageResponse,
GetDefaultLoginTextsRequest, GetDefaultLoginTextsRequest,
GetDefaultLoginTextsResponse, GetDefaultLoginTextsResponse,
GetDefaultOrgRequest,
GetDefaultOrgResponse,
GetDefaultPasswordChangeMessageTextRequest, GetDefaultPasswordChangeMessageTextRequest,
GetDefaultPasswordChangeMessageTextResponse, GetDefaultPasswordChangeMessageTextResponse,
GetDefaultPasswordlessRegistrationMessageTextRequest, GetDefaultPasswordlessRegistrationMessageTextRequest,
@ -429,6 +431,11 @@ export class AdminService {
this.storageService.getItem('onboarding-dismissed', StorageLocation.local) === 'true' ? true : false; this.storageService.getItem('onboarding-dismissed', StorageLocation.local) === 'true' ? true : false;
} }
public getDefaultOrg(): Promise<GetDefaultOrgResponse.AsObject> {
const req = new GetDefaultOrgRequest();
return this.grpcService.admin.getDefaultOrg(req, null).then((resp) => resp.toObject());
}
public setDefaultOrg(orgId: string): Promise<SetDefaultOrgResponse.AsObject> { public setDefaultOrg(orgId: string): Promise<SetDefaultOrgResponse.AsObject> {
const req = new SetDefaultOrgRequest(); const req = new SetDefaultOrgRequest();
req.setOrgId(orgId); req.setOrgId(orgId);

View File

@ -353,6 +353,7 @@ import {
RemoveOrgMetadataRequest, RemoveOrgMetadataRequest,
RemoveOrgMetadataResponse, RemoveOrgMetadataResponse,
RemoveOrgRequest, RemoveOrgRequest,
RemoveOrgResponse,
RemovePersonalAccessTokenRequest, RemovePersonalAccessTokenRequest,
RemovePersonalAccessTokenResponse, RemovePersonalAccessTokenResponse,
RemoveProjectGrantMemberRequest, RemoveProjectGrantMemberRequest,
@ -1749,7 +1750,7 @@ export class ManagementService {
return this.grpcService.mgmt.removeUser(req, null).then((resp) => resp.toObject()); return this.grpcService.mgmt.removeUser(req, null).then((resp) => resp.toObject());
} }
public removeOrg(): Promise<RemoveUserResponse.AsObject> { public removeOrg(): Promise<RemoveOrgResponse.AsObject> {
const req = new RemoveOrgRequest(); const req = new RemoveOrgRequest();
return this.grpcService.mgmt.removeOrg(req, null).then((resp) => resp.toObject()); return this.grpcService.mgmt.removeOrg(req, null).then((resp) => resp.toObject());
} }

View File

@ -1285,6 +1285,7 @@
"MEMBERCHANGED": "Сменен управител.", "MEMBERCHANGED": "Сменен управител.",
"SETPRIMARY": "Основен набор от домейни.", "SETPRIMARY": "Основен набор от домейни.",
"DELETED": "Организацията е изтрита успешно", "DELETED": "Организацията е изтрита успешно",
"DEFAULTORGNOTFOUND": "Организацията по подразбиране не беше намерена",
"ORG_WAS_DELETED": "Организацията е изтрита." "ORG_WAS_DELETED": "Организацията е изтрита."
}, },
"DIALOG": { "DIALOG": {

View File

@ -1292,6 +1292,7 @@
"MEMBERCHANGED": "Manažer změněn.", "MEMBERCHANGED": "Manažer změněn.",
"SETPRIMARY": "Nastavena primární doména.", "SETPRIMARY": "Nastavena primární doména.",
"DELETED": "Organizace úspěšně smazána", "DELETED": "Organizace úspěšně smazána",
"DEFAULTORGNOTFOUND": "Výchozí organizace nebyla nalezena",
"ORG_WAS_DELETED": "Organizace byla smazána." "ORG_WAS_DELETED": "Organizace byla smazána."
}, },
"DIALOG": { "DIALOG": {

View File

@ -1291,6 +1291,7 @@
"MEMBERCHANGED": "Manager geändert.", "MEMBERCHANGED": "Manager geändert.",
"SETPRIMARY": "Primäre Domain gesetzt.", "SETPRIMARY": "Primäre Domain gesetzt.",
"DELETED": "Organisation erfolgreich gelöscht", "DELETED": "Organisation erfolgreich gelöscht",
"DEFAULTORGNOTFOUND": "Die Standardorganisation wurde nicht gefunden",
"ORG_WAS_DELETED": "Organisation wurde gelöscht." "ORG_WAS_DELETED": "Organisation wurde gelöscht."
}, },
"DIALOG": { "DIALOG": {

View File

@ -1291,8 +1291,9 @@
"MEMBERREMOVED": "Manager removed.", "MEMBERREMOVED": "Manager removed.",
"MEMBERCHANGED": "Manager changed.", "MEMBERCHANGED": "Manager changed.",
"SETPRIMARY": "Primary domain set.", "SETPRIMARY": "Primary domain set.",
"DELETED": "Organisation deleted successfully", "DELETED": "Organization deleted successfully",
"ORG_WAS_DELETED": "Organisation has been deleted." "DEFAULTORGNOTFOUND": "The default organization was not found",
"ORG_WAS_DELETED": "Organization has been deleted."
}, },
"DIALOG": { "DIALOG": {
"DEACTIVATE": { "DEACTIVATE": {

View File

@ -1293,6 +1293,7 @@
"MEMBERCHANGED": "Mánager modificado.", "MEMBERCHANGED": "Mánager modificado.",
"SETPRIMARY": "Dominio primario establecido.", "SETPRIMARY": "Dominio primario establecido.",
"DELETED": "Organización borrada con éxito", "DELETED": "Organización borrada con éxito",
"DEFAULTORGNOTFOUND": "No se encontró la organización por defecto",
"ORG_WAS_DELETED": "La organización ha sido borrada." "ORG_WAS_DELETED": "La organización ha sido borrada."
}, },
"DIALOG": { "DIALOG": {

View File

@ -1291,6 +1291,7 @@
"MEMBERCHANGED": "Gestionnaire modifié.", "MEMBERCHANGED": "Gestionnaire modifié.",
"SETPRIMARY": "Domaine primaire défini.", "SETPRIMARY": "Domaine primaire défini.",
"DELETED": "Organisation supprimée avec succès", "DELETED": "Organisation supprimée avec succès",
"DEFAULTORGNOTFOUND": "L'organisation par défaut est introuvable",
"ORG_WAS_DELETED": "L'organisation a été supprimée" "ORG_WAS_DELETED": "L'organisation a été supprimée"
}, },
"DIALOG": { "DIALOG": {

View File

@ -1291,6 +1291,7 @@
"MEMBERCHANGED": "Manager cambiato con successo", "MEMBERCHANGED": "Manager cambiato con successo",
"SETPRIMARY": "Dominio primario cambiato con successo", "SETPRIMARY": "Dominio primario cambiato con successo",
"DELETED": "Organizzazione eliminata con successo", "DELETED": "Organizzazione eliminata con successo",
"DEFAULTORGNOTFOUND": "Impossibile trovare l'organizzazione predefinita",
"ORG_WAS_DELETED": "Organizzazione è stata eliminata" "ORG_WAS_DELETED": "Organizzazione è stata eliminata"
}, },
"DIALOG": { "DIALOG": {

View File

@ -1292,6 +1292,7 @@
"MEMBERCHANGED": "マネージャーが変更されました。", "MEMBERCHANGED": "マネージャーが変更されました。",
"SETPRIMARY": "プライマリドメインが設定されました。", "SETPRIMARY": "プライマリドメインが設定されました。",
"DELETED": "組織が正常に削除されました。", "DELETED": "組織が正常に削除されました。",
"DEFAULTORGNOTFOUND": "デフォルトの組織が見つかりませんでした",
"ORG_WAS_DELETED": "組織が削除されました。" "ORG_WAS_DELETED": "組織が削除されました。"
}, },
"DIALOG": { "DIALOG": {

View File

@ -1293,6 +1293,7 @@
"MEMBERCHANGED": "Променет менаџер.", "MEMBERCHANGED": "Променет менаџер.",
"SETPRIMARY": "Поставен основен домен.", "SETPRIMARY": "Поставен основен домен.",
"DELETED": "Организацијата успешно избришана", "DELETED": "Организацијата успешно избришана",
"DEFAULTORGNOTFOUND": "Стандардната организација не беше пронајдена",
"ORG_WAS_DELETED": "Организацијата е избришана." "ORG_WAS_DELETED": "Организацијата е избришана."
}, },
"DIALOG": { "DIALOG": {

View File

@ -1292,6 +1292,7 @@
"MEMBERCHANGED": "Beheerder gewijzigd.", "MEMBERCHANGED": "Beheerder gewijzigd.",
"SETPRIMARY": "Primaire domein ingesteld.", "SETPRIMARY": "Primaire domein ingesteld.",
"DELETED": "Organisatie succesvol verwijderd", "DELETED": "Organisatie succesvol verwijderd",
"DEFAULTORGNOTFOUND": "De standaardorganisatie is niet gevonden",
"ORG_WAS_DELETED": "Organisatie is verwijderd." "ORG_WAS_DELETED": "Organisatie is verwijderd."
}, },
"DIALOG": { "DIALOG": {

View File

@ -1291,6 +1291,7 @@
"MEMBERCHANGED": "Zmieniono managera.", "MEMBERCHANGED": "Zmieniono managera.",
"SETPRIMARY": "Ustawiono domenę podstawową.", "SETPRIMARY": "Ustawiono domenę podstawową.",
"DELETED": "Organizacja została usunięta pomyślnie", "DELETED": "Organizacja została usunięta pomyślnie",
"DEFAULTORGNOTFOUND": "Nie znaleziono organizacji domyślnej",
"ORG_WAS_DELETED": "Organizacja została usunięta." "ORG_WAS_DELETED": "Organizacja została usunięta."
}, },
"DIALOG": { "DIALOG": {

View File

@ -1293,6 +1293,7 @@
"MEMBERCHANGED": "Gerente alterado.", "MEMBERCHANGED": "Gerente alterado.",
"SETPRIMARY": "Domínio principal definido.", "SETPRIMARY": "Domínio principal definido.",
"DELETED": "Organização excluída com sucesso", "DELETED": "Organização excluída com sucesso",
"DEFAULTORGNOTFOUND": "A organização padrão não foi encontrada",
"ORG_WAS_DELETED": "Organização foi excluída." "ORG_WAS_DELETED": "Organização foi excluída."
}, },
"DIALOG": { "DIALOG": {

View File

@ -1335,6 +1335,7 @@
"MEMBERCHANGED": "Менеджер изменён.", "MEMBERCHANGED": "Менеджер изменён.",
"SETPRIMARY": "Установлен основной домен.", "SETPRIMARY": "Установлен основной домен.",
"DELETED": "Организация успешно удалена", "DELETED": "Организация успешно удалена",
"DEFAULTORGNOTFOUND": "Организация по умолчанию не найдена",
"ORG_WAS_DELETED": "Организация удалена." "ORG_WAS_DELETED": "Организация удалена."
}, },
"DIALOG": { "DIALOG": {

View File

@ -1291,6 +1291,7 @@
"MEMBERCHANGED": "管理者以改变。", "MEMBERCHANGED": "管理者以改变。",
"SETPRIMARY": "已设为主域名。", "SETPRIMARY": "已设为主域名。",
"DELETED": "成功删除的组织", "DELETED": "成功删除的组织",
"DEFAULTORGNOTFOUND": "未找到默认组织",
"ORG_WAS_DELETED": "组织被删除" "ORG_WAS_DELETED": "组织被删除"
}, },
"DIALOG": { "DIALOG": {

View File

@ -57,18 +57,6 @@ spec:
selector: selector:
app: cockroachdb app: cockroachdb
--- ---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: cockroachdb-budget
labels:
app: cockroachdb
spec:
selector:
matchLabels:
app: cockroachdb
maxUnavailable: 1
---
apiVersion: apps/v1 apiVersion: apps/v1
kind: StatefulSet kind: StatefulSet
metadata: metadata:

View File

@ -7,7 +7,7 @@ spec:
template: template:
metadata: metadata:
annotations: annotations:
client.knative.dev/user-image: ghcr.io/zitadel/zitadel:stable client.knative.dev/user-image: ghcr.io/zitadel/zitadel:latest
creationTimestamp: null creationTimestamp: null
spec: spec:
containerConcurrency: 0 containerConcurrency: 0
@ -28,7 +28,7 @@ spec:
value: "80" value: "80"
- name: ZITADEL_EXTERNALDOMAIN - name: ZITADEL_EXTERNALDOMAIN
value: zitadel.default.127.0.0.1.sslip.io value: zitadel.default.127.0.0.1.sslip.io
image: ghcr.io/zitadel/zitadel:stable image: ghcr.io/zitadel/zitadel:latest
name: user-container name: user-container
ports: ports:
- containerPort: 8080 - containerPort: 8080

View File

@ -68,6 +68,19 @@ https://github.com/zitadel/actions/blob/main/examples/custom_roles.js
</details> </details>
### Custom role mapping including org metadata in claims
There's even a possibility to use the metadata of organizations the user is granted to
<details open="">
<summary>Code example</summary>
```js reference
https://github.com/zitadel/actions/blob/main/examples/custom_roles_org_metadata.js
```
</details>
## Customize SAML response ## Customize SAML response
Append attributes returned on SAML requests. Append attributes returned on SAML requests.

View File

@ -210,3 +210,5 @@ This object represents a list of user grant stored in ZITADEL.
The name of the organization, where the user was granted The name of the organization, where the user was granted
- `projectId` *string* - `projectId` *string*
- `projectName` *string* - `projectName` *string*
- `getOrgMetadata()` [*metadataResult*](#metadata-result)
Get the metadata of the organization where the user was granted

View File

@ -1,23 +1,78 @@
--- ---
title: Identity Brokering in ZITADEL title: Identity Brokering
sidebar_label: Identity Brokering sidebar_label: Identity Brokering
--- ---
## What are Identity Brokering and Federated Identities? Link social logins and external identity providers with your identity management platform allowing users to login with their preferred identity provider.
Establish a trusted connection between your central identity provider (IdP) and third party identity providers.
By using a central identity brokering service you don't need to develop and establish a trust relationship between each application and each identity provider individually.
## What are federated identities?
Federated identity management is an arrangement built upon the trust between two or more domains. Users of these domains are allowed to access applications and services using the same identity. Federated identity management is an arrangement built upon the trust between two or more domains. Users of these domains are allowed to access applications and services using the same identity.
This identity is known as federated identity and the pattern behind this is identity federation. This identity is known as federated identity and the pattern behind this is identity federation.
Compatibility across various IdPs is ensured by using industry standard protocols, such as:
* OpenID Connect (OIDC): A modern and versatile protocol for secure authentication.
* SAML2: A widely adopted protocol for secure single sign-on (SSO) in enterprise environments.
* LDAP: A lightweight protocol for accessing user data directories commonly used in corporate networks.
## What is identity brokering?
A service provider that specializes in brokering access control between multiple service providers (also referred to as relying parties) is called an identity broker. A service provider that specializes in brokering access control between multiple service providers (also referred to as relying parties) is called an identity broker.
Federated identity management is an arrangement that is made between two or more such identity brokers across organizations. Federated identity management is an arrangement that is made between two or more such identity brokers across organizations.
For example, if Google is configured as an identity provider in your organization, the user will get the option to use his Google Account on the Login Screen of ZITADEL. Because Google is registered as a trusted identity provider, the user will be able to login in with the Google account after the user is linked with an existing ZITADEL account (if the user is already registered) or a new one with the claims provided by Google. For example, if Google is configured as an identity provider in your organization, the user will get the option to use his Google Account on the Login Screen of ZITADEL.
Because Google is registered as a trusted identity provider, the user will be able to login in with the Google account after the user is linked with an existing ZITADEL account (if the user is already registered) or a new one with the claims provided by Google.
![Identity Brokering](/img/guides/identity_brokering.png) ![Diagram of an identity brokering scheme using a central identity provider that has a trust link to the Google IdP and Entra ID](/img/concepts/features/identity-brokering.png)
## How to use external identity providers in ZITADEL The schema is a very simplified version, but shows the essential steps for identity brokering
Configure external identity providers on the instance level or just for one organization via the [Console](/guides/manage/console/default-settings#identity-providers) or ZITADEL APIs. 1. An unauthenticated user wants to use the alpha.com's application.
2. The application redirects the user to alpha.com's identity provider (IdP).
3. Based on the user's tenants configuration the IdP presents the configured identity providers, or redirects the user directly to the primary external IdP. The user authenticates with their external identity provider (eg, Entra ID).
4. After the authentication, the user is redirected back to alpha.com's identity provider. If the user doesn't exist in the IdP the user will be created just-in-time and linked to the external identity provider for future reference.
5. As with a local authentication, the IdP issues a token to the user that can be used to access the application. The IdP redirects the user, which is now authenticated, eventually to the application.
You will find [detailed integration guides for many Identity Providers](/guides/integrate/identity-providers/introduction) in our docs. ## Is single-sign-on (SSO) the same as identity brokering?
ZITADEL also provides templates to configure generic identity providers, which don't have templates.
Sometimes single-sign-on (SSO) and login with third party identity providers is used interchangeably.
Typically SSO describes an authentication scheme that allows users to log in once at a central identity provider and access service providers (client applications) without to login again.
Identity brokering describes an authentication scheme where users can login with external identity providers that have a established trust with an identity provider which facilitates the authentication for the requested applications.
The connection between the two lies in how SSO can be implemented as part of an identity brokering solution.
In such cases, the identity broker uses SSO to enable seamless access across multiple systems, handling the complexities of different authentication protocols and standards behind the scenes.
This allows users to log in once and gain access to multiple systems that the broker facilitates.
## Multitenancy and identity brokering
In a multi-tenancy application, you want to be able to configure an external identity provider per tenant.
For example some organizations might use their EntraID, some other want to login with their OKTA, or Google Workspace.
Using an identity provider with strong multitenancy capabilities such as ZITADEL, you can configure a different set of external identity providers per organization.
[Domain discovery](/docs/guides/solution-scenarios/domain-discovery) ensures that users are redirected to their external identity provider based on their email-address or username.
[Managers](../structure/managers) can configure organization domains that are used for domain-based redirection to an external IdP.
![Diagram explaining domain discovery](/img/concepts/features/domain-discovery.png)
## Simplify identity brokering with ZITADEL templates
ZITADEL works with SAML, OpenID Connect, and LDAP external identity providers.
For popular IdPs such as EntraID, Okta, Google, Facebook, and GitHub, ZITADEL [offers pre-configured templates](/docs/guides/integrate/identity-providers/introduction).
These templates expedite the configuration process, allowing organizations to quickly integrate these providers with minimal effort.
ZITADEL recognizes that specific needs may extend beyond pre-built templates.
To address this, ZITADEL provides generic templates that enable connection to virtually any IdP. This ensures maximum flexibility and future-proofs login infrastructure, accommodating future integrations with ease.
### References
* [Detailed integration guide for many identity providers](/guides/integrate/identity-providers/introduction)
* [Setup identity providers with Console](/guides/manage/console/default-settings#identity-providers)
* [Configure identity providers with the ZITADEL API](/docs/category/apis/resources/mgmt/identity-providers)

View File

@ -9,9 +9,13 @@ import CustomLoginPolicy from './_custom_login_policy.mdx';
import IDPsOverview from './_idps_overview.mdx'; import IDPsOverview from './_idps_overview.mdx';
import Activate from './_activate.mdx'; import Activate from './_activate.mdx';
import TestSetup from './_test_setup.mdx'; import TestSetup from './_test_setup.mdx';
import { ResponsivePlayer } from "../../../../src/components/player";
<Intro provider="Google"/> <Intro provider="Google"/>
<ResponsivePlayer playing controls url='https://www.youtube.com/watch?v=wg-ee-EnHdE' />
## Open the Google Identity Provider Template ## Open the Google Identity Provider Template
<IDPsOverview templates="Google"/> <IDPsOverview templates="Google"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

View File

@ -1,6 +1,7 @@
package object package object
import ( import (
"context"
"encoding/json" "encoding/json"
"time" "time"
@ -77,6 +78,21 @@ func UserMetadataListFromSlice(c *actions.FieldConfig, metadata []query.UserMeta
return c.Runtime.ToValue(result) return c.Runtime.ToValue(result)
} }
func GetOrganizationMetadata(ctx context.Context, queries *query.Queries, c *actions.FieldConfig, organizationID string) goja.Value {
metadata, err := queries.SearchOrgMetadata(
ctx,
true,
organizationID,
&query.OrgMetadataSearchQueries{},
false,
)
if err != nil {
logging.WithError(err).Info("unable to get org metadata in action")
panic(err)
}
return OrgMetadataListFromQuery(c, metadata)
}
func metadataByteArrayToValue(val []byte, runtime *goja.Runtime) goja.Value { func metadataByteArrayToValue(val []byte, runtime *goja.Runtime) goja.Value {
var value interface{} var value interface{}
if !json.Valid(val) { if !json.Valid(val) {

View File

@ -1,6 +1,7 @@
package object package object
import ( import (
"context"
"time" "time"
"github.com/dop251/goja" "github.com/dop251/goja"
@ -44,6 +45,8 @@ type userGrant struct {
ProjectId string ProjectId string
ProjectName string ProjectName string
GetOrgMetadata func(goja.FunctionCall) goja.Value
} }
func AppendGrantFunc(userGrants *UserGrants) func(c *actions.FieldConfig) func(call goja.FunctionCall) goja.Value { func AppendGrantFunc(userGrants *UserGrants) func(c *actions.FieldConfig) func(call goja.FunctionCall) goja.Value {
@ -58,10 +61,11 @@ func AppendGrantFunc(userGrants *UserGrants) func(c *actions.FieldConfig) func(c
} }
} }
func UserGrantsFromQuery(c *actions.FieldConfig, userGrants *query.UserGrants) goja.Value { func UserGrantsFromQuery(ctx context.Context, queries *query.Queries, c *actions.FieldConfig, userGrants *query.UserGrants) goja.Value {
if userGrants == nil { if userGrants == nil {
return c.Runtime.ToValue(nil) return c.Runtime.ToValue(nil)
} }
orgMetadata := make(map[string]goja.Value)
grantList := &userGrantList{ grantList := &userGrantList{
Count: userGrants.Count, Count: userGrants.Count,
Sequence: userGrants.Sequence, Sequence: userGrants.Sequence,
@ -84,16 +88,24 @@ func UserGrantsFromQuery(c *actions.FieldConfig, userGrants *query.UserGrants) g
UserGrantResourceOwnerName: grant.OrgName, UserGrantResourceOwnerName: grant.OrgName,
ProjectId: grant.ProjectID, ProjectId: grant.ProjectID,
ProjectName: grant.ProjectName, ProjectName: grant.ProjectName,
GetOrgMetadata: func(call goja.FunctionCall) goja.Value {
if md, ok := orgMetadata[grant.ResourceOwner]; ok {
return md
}
orgMetadata[grant.ResourceOwner] = GetOrganizationMetadata(ctx, queries, c, grant.ResourceOwner)
return orgMetadata[grant.ResourceOwner]
},
} }
} }
return c.Runtime.ToValue(grantList) return c.Runtime.ToValue(grantList)
} }
func UserGrantsFromSlice(c *actions.FieldConfig, userGrants []query.UserGrant) goja.Value { func UserGrantsFromSlice(ctx context.Context, queries *query.Queries, c *actions.FieldConfig, userGrants []query.UserGrant) goja.Value {
if userGrants == nil { if userGrants == nil {
return c.Runtime.ToValue(nil) return c.Runtime.ToValue(nil)
} }
orgMetadata := make(map[string]goja.Value)
grantList := &userGrantList{ grantList := &userGrantList{
Count: uint64(len(userGrants)), Count: uint64(len(userGrants)),
Grants: make([]*userGrant, len(userGrants)), Grants: make([]*userGrant, len(userGrants)),
@ -114,6 +126,13 @@ func UserGrantsFromSlice(c *actions.FieldConfig, userGrants []query.UserGrant) g
UserGrantResourceOwnerName: grant.OrgName, UserGrantResourceOwnerName: grant.OrgName,
ProjectId: grant.ProjectID, ProjectId: grant.ProjectID,
ProjectName: grant.ProjectName, ProjectName: grant.ProjectName,
GetOrgMetadata: func(goja.FunctionCall) goja.Value {
if md, ok := orgMetadata[grant.ResourceOwner]; ok {
return md
}
orgMetadata[grant.ResourceOwner] = GetOrganizationMetadata(ctx, queries, c, grant.ResourceOwner)
return orgMetadata[grant.ResourceOwner]
},
} }
} }

View File

@ -65,7 +65,7 @@ func (s *Server) ResendMyEmailVerification(ctx context.Context, _ *auth_pb.Resen
if err != nil { if err != nil {
return nil, err return nil, err
} }
objectDetails, err := s.command.CreateHumanEmailVerificationCode(ctx, ctxData.UserID, ctxData.ResourceOwner, emailCodeGenerator) objectDetails, err := s.command.CreateHumanEmailVerificationCode(ctx, ctxData.UserID, ctxData.ResourceOwner, emailCodeGenerator, "")
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -473,7 +473,7 @@ func (s *Server) ResendHumanInitialization(ctx context.Context, req *mgmt_pb.Res
if err != nil { if err != nil {
return nil, err return nil, err
} }
details, err := s.command.ResendInitialMail(ctx, req.UserId, domain.EmailAddress(req.Email), authz.GetCtxData(ctx).OrgID, initCodeGenerator) details, err := s.command.ResendInitialMail(ctx, req.UserId, domain.EmailAddress(req.Email), authz.GetCtxData(ctx).OrgID, initCodeGenerator, "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -487,7 +487,7 @@ func (s *Server) ResendHumanEmailVerification(ctx context.Context, req *mgmt_pb.
if err != nil { if err != nil {
return nil, err return nil, err
} }
objectDetails, err := s.command.CreateHumanEmailVerificationCode(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, emailCodeGenerator) objectDetails, err := s.command.CreateHumanEmailVerificationCode(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, emailCodeGenerator, "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -590,7 +590,7 @@ func (s *Server) SendHumanResetPasswordNotification(ctx context.Context, req *mg
if err != nil { if err != nil {
return nil, err return nil, err
} }
objectDetails, err := s.command.RequestSetPassword(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, notifyTypeToDomain(req.Type), passwordCodeGenerator) objectDetails, err := s.command.RequestSetPassword(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, notifyTypeToDomain(req.Type), passwordCodeGenerator, "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -774,13 +774,22 @@ func (s *Server) ListMachineKeys(ctx context.Context, req *mgmt_pb.ListMachineKe
func (s *Server) AddMachineKey(ctx context.Context, req *mgmt_pb.AddMachineKeyRequest) (*mgmt_pb.AddMachineKeyResponse, error) { func (s *Server) AddMachineKey(ctx context.Context, req *mgmt_pb.AddMachineKeyRequest) (*mgmt_pb.AddMachineKeyResponse, error) {
machineKey := AddMachineKeyRequestToCommand(req, authz.GetCtxData(ctx).OrgID) machineKey := AddMachineKeyRequestToCommand(req, authz.GetCtxData(ctx).OrgID)
// If there is no pubkey supplied, then AddUserMachineKey will generate a new one
pubkeySupplied := len(machineKey.PublicKey) > 0
details, err := s.command.AddUserMachineKey(ctx, machineKey) details, err := s.command.AddUserMachineKey(ctx, machineKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
keyDetails, err := machineKey.Detail()
if err != nil { // Return key details only if the pubkey wasn't supplied, otherwise the user already has
return nil, err // private key locally
var keyDetails []byte
if !pubkeySupplied {
var err error
keyDetails, err = machineKey.Detail()
if err != nil {
return nil, err
}
} }
return &mgmt_pb.AddMachineKeyResponse{ return &mgmt_pb.AddMachineKeyResponse{
KeyId: machineKey.KeyID, KeyId: machineKey.KeyID,

View File

@ -237,6 +237,7 @@ func AddMachineKeyRequestToCommand(req *mgmt_pb.AddMachineKeyRequest, resourceOw
}, },
ExpirationDate: expDate, ExpirationDate: expDate,
Type: authn.KeyTypeToDomain(req.Type), Type: authn.KeyTypeToDomain(req.Type),
PublicKey: req.PublicKey,
} }
} }

View File

@ -490,25 +490,16 @@ func (o *OPStorage) userinfoFlows(ctx context.Context, user *query.User, userGra
return object.UserMetadataListFromQuery(c, metadata) return object.UserMetadataListFromQuery(c, metadata)
} }
}), }),
actions.SetFields("grants", func(c *actions.FieldConfig) interface{} { actions.SetFields("grants",
return object.UserGrantsFromQuery(c, userGrants) func(c *actions.FieldConfig) interface{} {
}), return object.UserGrantsFromQuery(ctx, o.query, c, userGrants)
},
),
), ),
actions.SetFields("org", actions.SetFields("org",
actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} { actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
return func(goja.FunctionCall) goja.Value { return func(goja.FunctionCall) goja.Value {
metadata, err := o.query.SearchOrgMetadata( return object.GetOrganizationMetadata(ctx, o.query, c, user.ResourceOwner)
ctx,
true,
user.ResourceOwner,
&query.OrgMetadataSearchQueries{},
false,
)
if err != nil {
logging.WithError(err).Info("unable to get org metadata in action")
panic(err)
}
return object.OrgMetadataListFromQuery(c, metadata)
} }
}), }),
), ),
@ -714,24 +705,13 @@ func (o *OPStorage) privateClaimsFlows(ctx context.Context, userID string, userG
} }
}), }),
actions.SetFields("grants", func(c *actions.FieldConfig) interface{} { actions.SetFields("grants", func(c *actions.FieldConfig) interface{} {
return object.UserGrantsFromQuery(c, userGrants) return object.UserGrantsFromQuery(ctx, o.query, c, userGrants)
}), }),
), ),
actions.SetFields("org", actions.SetFields("org",
actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} { actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
return func(goja.FunctionCall) goja.Value { return func(goja.FunctionCall) goja.Value {
metadata, err := o.query.SearchOrgMetadata( return object.GetOrganizationMetadata(ctx, o.query, c, user.ResourceOwner)
ctx,
true,
user.ResourceOwner,
&query.OrgMetadataSearchQueries{},
false,
)
if err != nil {
logging.WithError(err).Info("unable to get org metadata in action")
panic(err)
}
return object.OrgMetadataListFromQuery(c, metadata)
} }
}), }),
), ),

View File

@ -0,0 +1,76 @@
//go:build integration
package oidc_test
import (
"io"
"net/http"
"net/url"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/oidc/v3/pkg/client"
"github.com/zitadel/oidc/v3/pkg/client/rp"
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/schema"
)
func TestServer_RefreshToken_Status(t *testing.T) {
clientID, _ := createClient(t)
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
tests := []struct {
name string
refreshToken string
}{
{
name: "invalid base64",
refreshToken: "~!~@#$%",
},
{
name: "invalid after decrypt",
refreshToken: "DEADBEEFDEADBEEF",
},
{
name: "short input",
refreshToken: "DEAD",
},
{
name: "empty input",
refreshToken: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
request := rp.RefreshTokenRequest{
RefreshToken: tt.refreshToken,
ClientID: clientID,
GrantType: oidc.GrantTypeRefreshToken,
}
client.CallTokenEndpoint(CTX, request, tokenEndpointCaller{RelyingParty: provider})
values := make(url.Values)
err := schema.NewEncoder().Encode(request, values)
require.NoError(t, err)
resp, err := http.Post(provider.OAuthConfig().Endpoint.TokenURL, "application/x-www-form-urlencoded", strings.NewReader(values.Encode()))
require.NoError(t, err)
defer resp.Body.Close()
assert.Less(t, resp.StatusCode, 500)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
t.Log(string(body))
})
}
}
type tokenEndpointCaller struct {
rp.RelyingParty
}
func (t tokenEndpointCaller) TokenEndpoint() string {
return t.OAuthConfig().Endpoint.TokenURL
}

View File

@ -252,24 +252,13 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user
} }
}), }),
actions.SetFields("grants", func(c *actions.FieldConfig) interface{} { actions.SetFields("grants", func(c *actions.FieldConfig) interface{} {
return object.UserGrantsFromSlice(c, qu.UserGrants) return object.UserGrantsFromSlice(ctx, s.query, c, qu.UserGrants)
}), }),
), ),
actions.SetFields("org", actions.SetFields("org",
actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} { actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
return func(goja.FunctionCall) goja.Value { return func(goja.FunctionCall) goja.Value {
metadata, err := s.query.SearchOrgMetadata( return object.GetOrganizationMetadata(ctx, s.query, c, qu.User.ResourceOwner)
ctx,
true,
qu.User.ResourceOwner,
&query.OrgMetadataSearchQueries{},
false,
)
if err != nil {
logging.WithError(err).Info("unable to get org metadata in action")
panic(err)
}
return object.OrgMetadataListFromQuery(c, metadata)
} }
}), }),
), ),

View File

@ -246,24 +246,13 @@ func (p *Storage) getCustomAttributes(ctx context.Context, user *query.User, use
} }
}), }),
actions.SetFields("grants", func(c *actions.FieldConfig) interface{} { actions.SetFields("grants", func(c *actions.FieldConfig) interface{} {
return object.UserGrantsFromQuery(c, userGrants) return object.UserGrantsFromQuery(ctx, p.query, c, userGrants)
}), }),
), ),
actions.SetFields("org", actions.SetFields("org",
actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} { actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
return func(goja.FunctionCall) goja.Value { return func(goja.FunctionCall) goja.Value {
metadata, err := p.query.SearchOrgMetadata( return object.GetOrganizationMetadata(ctx, p.query, c, user.ResourceOwner)
ctx,
true,
user.ResourceOwner,
&query.OrgMetadataSearchQueries{},
false,
)
if err != nil {
logging.WithError(err).Info("unable to get org metadata in action")
panic(err)
}
return object.OrgMetadataListFromQuery(c, metadata)
} }
}), }),
), ),

View File

@ -3,6 +3,8 @@ package login
import ( import (
"net/http" "net/http"
"github.com/zitadel/logging"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware" http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
) )
@ -33,3 +35,23 @@ func (l *Login) getAuthRequestAndParseData(r *http.Request, data interface{}) (*
func (l *Login) getParseData(r *http.Request, data interface{}) error { func (l *Login) getParseData(r *http.Request, data interface{}) error {
return l.parser.Parse(r, data) return l.parser.Parse(r, data)
} }
// checkOptionalAuthRequestOfEmailLinks tries to get the [domain.AuthRequest] from the request.
// In case any error occurs, e.g. if the user agent does not correspond, the `authRequestID` query parameter will be
// removed from the request URL and form to ensure subsequent functions and pages do not use it.
// This function is used for handling links in emails, which could possibly be opened on another device than the
// auth request was initiated.
func (l *Login) checkOptionalAuthRequestOfEmailLinks(r *http.Request) *domain.AuthRequest {
authReq, err := l.getAuthRequest(r)
if err == nil {
return authReq
}
logging.WithError(err).Infof("authrequest could not be found for email link on path %s", r.URL.RequestURI())
queries := r.URL.Query()
queries.Del(QueryAuthRequestID)
r.URL.RawQuery = queries.Encode()
r.RequestURI = r.URL.RequestURI()
r.Form.Del(QueryAuthRequestID)
r.PostForm.Del(QueryAuthRequestID)
return nil
}

View File

@ -1,8 +1,8 @@
package login package login
import ( import (
"fmt"
"net/http" "net/http"
"net/url"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware" http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
@ -38,14 +38,20 @@ type initPasswordData struct {
HasSymbol string HasSymbol string
} }
func InitPasswordLink(origin, userID, code, orgID string) string { func InitPasswordLink(origin, userID, code, orgID, authRequestID string) string {
return fmt.Sprintf("%s%s?userID=%s&code=%s&orgID=%s", externalLink(origin), EndpointInitPassword, userID, code, orgID) v := url.Values{}
v.Set(queryInitPWUserID, userID)
v.Set(queryInitPWCode, code)
v.Set(queryOrgID, orgID)
v.Set(QueryAuthRequestID, authRequestID)
return externalLink(origin) + EndpointInitPassword + "?" + v.Encode()
} }
func (l *Login) handleInitPassword(w http.ResponseWriter, r *http.Request) { func (l *Login) handleInitPassword(w http.ResponseWriter, r *http.Request) {
authReq := l.checkOptionalAuthRequestOfEmailLinks(r)
userID := r.FormValue(queryInitPWUserID) userID := r.FormValue(queryInitPWUserID)
code := r.FormValue(queryInitPWCode) code := r.FormValue(queryInitPWCode)
l.renderInitPassword(w, r, nil, userID, code, nil) l.renderInitPassword(w, r, authReq, userID, code, nil)
} }
func (l *Login) handleInitPasswordCheck(w http.ResponseWriter, r *http.Request) { func (l *Login) handleInitPasswordCheck(w http.ResponseWriter, r *http.Request) {
@ -94,7 +100,7 @@ func (l *Login) resendPasswordSet(w http.ResponseWriter, r *http.Request, authRe
l.renderInitPassword(w, r, authReq, userID, "", err) l.renderInitPassword(w, r, authReq, userID, "", err)
return return
} }
_, err = l.command.RequestSetPassword(setContext(r.Context(), userOrg), userID, userOrg, domain.NotificationTypeEmail, passwordCodeGenerator) _, err = l.command.RequestSetPassword(setContext(r.Context(), userOrg), userID, userOrg, domain.NotificationTypeEmail, passwordCodeGenerator, authReq.ID)
l.renderInitPassword(w, r, authReq, userID, "", err) l.renderInitPassword(w, r, authReq, userID, "", err)
} }

View File

@ -1,8 +1,8 @@
package login package login
import ( import (
"fmt"
"net/http" "net/http"
"net/url"
"strconv" "strconv"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware" http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
@ -44,16 +44,24 @@ type initUserData struct {
HasSymbol string HasSymbol string
} }
func InitUserLink(origin, userID, loginName, code, orgID string, passwordSet bool) string { func InitUserLink(origin, userID, loginName, code, orgID string, passwordSet bool, authRequestID string) string {
return fmt.Sprintf("%s%s?userID=%s&loginname=%s&code=%s&orgID=%s&passwordset=%t", externalLink(origin), EndpointInitUser, userID, loginName, code, orgID, passwordSet) v := url.Values{}
v.Set(queryInitUserUserID, userID)
v.Set(queryInitUserLoginName, loginName)
v.Set(queryInitUserCode, code)
v.Set(queryOrgID, orgID)
v.Set(queryInitUserPassword, strconv.FormatBool(passwordSet))
v.Set(QueryAuthRequestID, authRequestID)
return externalLink(origin) + EndpointInitUser + "?" + v.Encode()
} }
func (l *Login) handleInitUser(w http.ResponseWriter, r *http.Request) { func (l *Login) handleInitUser(w http.ResponseWriter, r *http.Request) {
authReq := l.checkOptionalAuthRequestOfEmailLinks(r)
userID := r.FormValue(queryInitUserUserID) userID := r.FormValue(queryInitUserUserID)
code := r.FormValue(queryInitUserCode) code := r.FormValue(queryInitUserCode)
loginName := r.FormValue(queryInitUserLoginName) loginName := r.FormValue(queryInitUserLoginName)
passwordSet, _ := strconv.ParseBool(r.FormValue(queryInitUserPassword)) passwordSet, _ := strconv.ParseBool(r.FormValue(queryInitUserPassword))
l.renderInitUser(w, r, nil, userID, loginName, code, passwordSet, nil) l.renderInitUser(w, r, authReq, userID, loginName, code, passwordSet, nil)
} }
func (l *Login) handleInitUserCheck(w http.ResponseWriter, r *http.Request) { func (l *Login) handleInitUserCheck(w http.ResponseWriter, r *http.Request) {
@ -105,7 +113,7 @@ func (l *Login) resendUserInit(w http.ResponseWriter, r *http.Request, authReq *
l.renderInitUser(w, r, authReq, userID, loginName, "", showPassword, err) l.renderInitUser(w, r, authReq, userID, loginName, "", showPassword, err)
return return
} }
_, err = l.command.ResendInitialMail(setContext(r.Context(), userOrgID), userID, "", userOrgID, initCodeGenerator) _, err = l.command.ResendInitialMail(setContext(r.Context(), userOrgID), userID, "", userOrgID, initCodeGenerator, authReq.ID)
l.renderInitUser(w, r, authReq, userID, loginName, "", showPassword, err) l.renderInitUser(w, r, authReq, userID, loginName, "", showPassword, err)
} }

View File

@ -1,8 +1,8 @@
package login package login
import ( import (
"fmt"
"net/http" "net/http"
"net/url"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
) )
@ -27,18 +27,24 @@ type mailVerificationData struct {
UserID string UserID string
} }
func MailVerificationLink(origin, userID, code, orgID string) string { func MailVerificationLink(origin, userID, code, orgID, authRequestID string) string {
return fmt.Sprintf("%s%s?userID=%s&code=%s&orgID=%s", externalLink(origin), EndpointMailVerification, userID, code, orgID) v := url.Values{}
v.Set(queryUserID, userID)
v.Set(queryCode, code)
v.Set(queryOrgID, orgID)
v.Set(QueryAuthRequestID, authRequestID)
return externalLink(origin) + EndpointMailVerification + "?" + v.Encode()
} }
func (l *Login) handleMailVerification(w http.ResponseWriter, r *http.Request) { func (l *Login) handleMailVerification(w http.ResponseWriter, r *http.Request) {
authReq := l.checkOptionalAuthRequestOfEmailLinks(r)
userID := r.FormValue(queryUserID) userID := r.FormValue(queryUserID)
code := r.FormValue(queryCode) code := r.FormValue(queryCode)
if code != "" { if code != "" {
l.checkMailCode(w, r, nil, userID, code) l.checkMailCode(w, r, authReq, userID, code)
return return
} }
l.renderMailVerification(w, r, nil, userID, nil) l.renderMailVerification(w, r, authReq, userID, nil)
} }
func (l *Login) handleMailVerificationCheck(w http.ResponseWriter, r *http.Request) { func (l *Login) handleMailVerificationCheck(w http.ResponseWriter, r *http.Request) {
@ -61,7 +67,7 @@ func (l *Login) handleMailVerificationCheck(w http.ResponseWriter, r *http.Reque
l.checkMailCode(w, r, authReq, data.UserID, data.Code) l.checkMailCode(w, r, authReq, data.UserID, data.Code)
return return
} }
_, err = l.command.CreateHumanEmailVerificationCode(setContext(r.Context(), userOrg), data.UserID, userOrg, emailCodeGenerator) _, err = l.command.CreateHumanEmailVerificationCode(setContext(r.Context(), userOrg), data.UserID, userOrg, emailCodeGenerator, authReq.ID)
l.renderMailVerification(w, r, authReq, data.UserID, err) l.renderMailVerification(w, r, authReq, data.UserID, err)
} }

View File

@ -33,7 +33,7 @@ func (l *Login) handlePasswordReset(w http.ResponseWriter, r *http.Request) {
l.renderPasswordResetDone(w, r, authReq, err) l.renderPasswordResetDone(w, r, authReq, err)
return return
} }
_, err = l.command.RequestSetPassword(setContext(r.Context(), authReq.UserOrgID), user.ID, authReq.UserOrgID, domain.NotificationTypeEmail, passwordCodeGenerator) _, err = l.command.RequestSetPassword(setContext(r.Context(), authReq.UserOrgID), user.ID, authReq.UserOrgID, domain.NotificationTypeEmail, passwordCodeGenerator, authReq.ID)
l.renderPasswordResetDone(w, r, authReq, err) l.renderPasswordResetDone(w, r, authReq, err)
} }

View File

@ -7,6 +7,7 @@ import (
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware" http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
@ -67,22 +68,6 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
if authRequest != nil && authRequest.RequestedOrgID != "" && authRequest.RequestedOrgID != resourceOwner { if authRequest != nil && authRequest.RequestedOrgID != "" && authRequest.RequestedOrgID != resourceOwner {
resourceOwner = authRequest.RequestedOrgID resourceOwner = authRequest.RequestedOrgID
} }
initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeInitCode, l.userCodeAlg)
if err != nil {
l.renderRegister(w, r, authRequest, data, err)
return
}
emailCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyEmailCode, l.userCodeAlg)
if err != nil {
l.renderRegister(w, r, authRequest, data, err)
return
}
phoneCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyPhoneCode, l.userCodeAlg)
if err != nil {
l.renderRegister(w, r, authRequest, data, err)
return
}
// For consistency with the external authentication flow, // For consistency with the external authentication flow,
// the setMetadata() function is provided on the pre creation hook, for now, // the setMetadata() function is provided on the pre creation hook, for now,
// like for the ExternalAuthentication flow. // like for the ExternalAuthentication flow.
@ -96,22 +81,14 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
l.renderRegister(w, r, authRequest, data, err) l.renderRegister(w, r, authRequest, data, err)
return return
} }
user, err = l.command.RegisterHuman(setContext(r.Context(), resourceOwner), resourceOwner, user, nil, nil, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
human := command.AddHumanFromDomain(user, metadatas, authRequest, nil)
err = l.command.AddUserHuman(setContext(r.Context(), resourceOwner), resourceOwner, human, true, l.userCodeAlg)
if err != nil { if err != nil {
l.renderRegister(w, r, authRequest, data, err) l.renderRegister(w, r, authRequest, data, err)
return return
} }
userGrants, err := l.runPostCreationActions(human.ID, authRequest, r, resourceOwner, domain.FlowTypeInternalAuthentication)
if len(metadatas) > 0 {
_, err = l.command.BulkSetUserMetadata(r.Context(), user.AggregateID, resourceOwner, metadatas...)
if err != nil {
// TODO: What if action is configured to be allowed to fail? Same question for external registration.
l.renderRegister(w, r, authRequest, data, err)
return
}
}
userGrants, err := l.runPostCreationActions(user.AggregateID, authRequest, r, resourceOwner, domain.FlowTypeInternalAuthentication)
if err != nil { if err != nil {
l.renderError(w, r, authRequest, err) l.renderError(w, r, authRequest, err)
return return
@ -128,7 +105,7 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
return return
} }
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
err = l.authRepo.SelectUser(r.Context(), authRequest.ID, user.AggregateID, userAgentID) err = l.authRepo.SelectUser(r.Context(), authRequest.ID, human.ID, userAgentID)
if err != nil { if err != nil {
l.renderRegister(w, r, authRequest, data, err) l.renderRegister(w, r, authRequest, data, err)
return return

View File

@ -543,23 +543,19 @@ func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, regis
if err != nil { if err != nil {
return err return err
} }
initCodeGenerator, err := repo.Query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, repo.UserCodeAlg) addMetadata := make([]*command.AddMetadataEntry, len(metadatas))
for i, metadata := range metadatas {
addMetadata[i] = &command.AddMetadataEntry{
Key: metadata.Key,
Value: metadata.Value,
}
}
human := command.AddHumanFromDomain(registerUser, metadatas, request, externalIDP)
err = repo.Command.AddUserHuman(ctx, resourceOwner, human, true, repo.UserCodeAlg)
if err != nil { if err != nil {
return err return err
} }
emailCodeGenerator, err := repo.Query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, repo.UserCodeAlg) request.SetUserInfo(human.ID, human.Username, human.Username, human.DisplayName, "", resourceOwner)
if err != nil {
return err
}
phoneCodeGenerator, err := repo.Query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, repo.UserCodeAlg)
if err != nil {
return err
}
human, err := repo.Command.RegisterHuman(ctx, resourceOwner, registerUser, externalIDP, orgMemberRoles, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
if err != nil {
return err
}
request.SetUserInfo(human.AggregateID, human.Username, human.PreferredLoginName, human.DisplayName, "", human.ResourceOwner)
request.SelectedIDPConfigID = externalIDP.IDPConfigID request.SelectedIDPConfigID = externalIDP.IDPConfigID
request.LinkingUsers = nil request.LinkingUsers = nil
err = repo.Command.UserIDPLoginChecked(ctx, request.UserOrgID, request.UserID, request.WithCurrentInfo(info)) err = repo.Command.UserIDPLoginChecked(ctx, request.UserOrgID, request.UserID, request.WithCurrentInfo(info))

View File

@ -160,6 +160,10 @@ func (s *UserSession) Reducers() []handler.AggregateReducer {
Event: user.UserRemovedType, Event: user.UserRemovedType,
Reduce: s.Reduce, Reduce: s.Reduce,
}, },
{
Event: user.HumanRegisteredType,
Reduce: s.Reduce,
},
}, },
}, },
{ {
@ -234,6 +238,8 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
handler.NewCol("multi_factor_verification_type", domain.MFALevelNotSetUp), handler.NewCol("multi_factor_verification_type", domain.MFALevelNotSetUp),
handler.NewCol("external_login_verification", time.Time{}), handler.NewCol("external_login_verification", time.Time{}),
handler.NewCol("state", domain.UserSessionStateTerminated), handler.NewCol("state", domain.UserSessionStateTerminated),
handler.NewCol("change_date", event.CreatedAt()),
handler.NewCol("sequence", event.Sequence()),
}, },
[]handler.Condition{ []handler.Condition{
handler.NewCond("instance_id", event.Aggregate().InstanceID), handler.NewCond("instance_id", event.Aggregate().InstanceID),
@ -247,16 +253,30 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
if err != nil { if err != nil {
return nil, err return nil, err
} }
return handler.NewUpdateStatement(event, return handler.NewMultiStatement(event,
[]handler.Column{ handler.AddUpdateStatement(
handler.NewCol("password_verification", time.Time{}), []handler.Column{
}, handler.NewCol("password_verification", event.CreatedAt()),
[]handler.Condition{ handler.NewCol("change_date", event.CreatedAt()),
handler.NewCond("instance_id", event.Aggregate().InstanceID), handler.NewCol("sequence", event.Sequence()),
handler.NewCond("user_id", event.Aggregate().ID), },
handler.Not(handler.NewCond("user_agent_id", userAgent)), []handler.Condition{
handler.Not(handler.NewCond("state", domain.UserSessionStateTerminated)), handler.NewCond("instance_id", event.Aggregate().InstanceID),
}, handler.NewCond("user_id", event.Aggregate().ID),
handler.NewCond("user_agent_id", userAgent),
}),
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol("password_verification", time.Time{}),
handler.NewCol("change_date", event.CreatedAt()),
handler.NewCol("sequence", event.Sequence()),
},
[]handler.Condition{
handler.NewCond("instance_id", event.Aggregate().InstanceID),
handler.NewCond("user_id", event.Aggregate().ID),
handler.Not(handler.NewCond("user_agent_id", userAgent)),
handler.Not(handler.NewCond("state", domain.UserSessionStateTerminated)),
}),
), nil ), nil
case user.UserV1MFAOTPRemovedType, case user.UserV1MFAOTPRemovedType,
user.HumanMFAOTPRemovedType, user.HumanMFAOTPRemovedType,
@ -264,6 +284,8 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
return handler.NewUpdateStatement(event, return handler.NewUpdateStatement(event,
[]handler.Column{ []handler.Column{
handler.NewCol("second_factor_verification", time.Time{}), handler.NewCol("second_factor_verification", time.Time{}),
handler.NewCol("change_date", event.CreatedAt()),
handler.NewCol("sequence", event.Sequence()),
}, },
[]handler.Condition{ []handler.Condition{
handler.NewCond("instance_id", event.Aggregate().InstanceID), handler.NewCond("instance_id", event.Aggregate().InstanceID),
@ -277,6 +299,8 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
[]handler.Column{ []handler.Column{
handler.NewCol("external_login_verification", time.Time{}), handler.NewCol("external_login_verification", time.Time{}),
handler.NewCol("selected_idp_config_id", ""), handler.NewCol("selected_idp_config_id", ""),
handler.NewCol("change_date", event.CreatedAt()),
handler.NewCol("sequence", event.Sequence()),
}, },
[]handler.Condition{ []handler.Condition{
handler.NewCond("instance_id", event.Aggregate().InstanceID), handler.NewCond("instance_id", event.Aggregate().InstanceID),
@ -289,6 +313,8 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
[]handler.Column{ []handler.Column{
handler.NewCol("passwordless_verification", time.Time{}), handler.NewCol("passwordless_verification", time.Time{}),
handler.NewCol("multi_factor_verification", time.Time{}), handler.NewCol("multi_factor_verification", time.Time{}),
handler.NewCol("change_date", event.CreatedAt()),
handler.NewCol("sequence", event.Sequence()),
}, },
[]handler.Condition{ []handler.Condition{
handler.NewCond("instance_id", event.Aggregate().InstanceID), handler.NewCond("instance_id", event.Aggregate().InstanceID),
@ -300,6 +326,23 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
return u.view.DeleteUserSessions(event.Aggregate().ID, event.Aggregate().InstanceID) return u.view.DeleteUserSessions(event.Aggregate().ID, event.Aggregate().InstanceID)
}), nil }), nil
case user.HumanRegisteredType:
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
eventData, err := view_model.UserSessionFromEvent(event)
if err != nil {
return err
}
session := &view_model.UserSessionView{
CreationDate: event.CreatedAt(),
ResourceOwner: event.Aggregate().ResourceOwner,
UserAgentID: eventData.UserAgentID,
UserID: event.Aggregate().ID,
State: int32(domain.UserSessionStateActive),
InstanceID: event.Aggregate().InstanceID,
PasswordVerification: event.CreatedAt(),
}
return u.updateSession(session, event)
}), nil
case instance.InstanceRemovedEventType: case instance.InstanceRemovedEventType:
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
return u.view.DeleteInstanceUserSessions(event.Aggregate().InstanceID) return u.view.DeleteInstanceUserSessions(event.Aggregate().InstanceID)

View File

@ -1427,6 +1427,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) {
Crypted: []byte("userinit"), Crypted: []byte("userinit"),
}, },
1*time.Hour, 1*time.Hour,
"",
), ),
), ),
eventFromEventPusher(org.NewMemberAddedEvent(context.Background(), eventFromEventPusher(org.NewMemberAddedEvent(context.Background(),

View File

@ -10,7 +10,6 @@ import (
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"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/v1/models"
"github.com/zitadel/zitadel/internal/repository/user" "github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
@ -57,7 +56,13 @@ type AddHuman struct {
Passwordless bool Passwordless bool
ExternalIDP bool ExternalIDP bool
Register bool Register bool
Metadata []*AddMetadataEntry // UserAgentID is optional and can be passed in case the user registered themselves.
// This will be used in the login UI to handle authentication automatically.
UserAgentID string
// AuthRequestID is optional and can be passed in case the user registered themselves.
// This will be used to pass the information in notifications for links to the login UI.
AuthRequestID string
Metadata []*AddMetadataEntry
// Links are optional // Links are optional
Links []*AddLink Links []*AddLink
@ -200,6 +205,7 @@ func (c *Commands) AddHumanCommand(human *AddHuman, orgID string, hasher *crypto
human.Gender, human.Gender,
human.Email.Address, human.Email.Address,
domainPolicy.UserLoginMustBeDomain, domainPolicy.UserLoginMustBeDomain,
"", // no user agent id available
) )
} else { } else {
createCmd = user.NewHumanAddedEvent( createCmd = user.NewHumanAddedEvent(
@ -272,7 +278,7 @@ func (c *Commands) addHumanCommandEmail(ctx context.Context, filter preparation.
if err != nil { if err != nil {
return nil, err return nil, err
} }
return append(cmds, user.NewHumanInitialCodeAddedEvent(ctx, &a.Aggregate, initCode.Crypted, initCode.Expiry)), nil return append(cmds, user.NewHumanInitialCodeAddedEvent(ctx, &a.Aggregate, initCode.Crypted, initCode.Expiry, human.AuthRequestID)), nil
} }
if !human.Email.Verified { if !human.Email.Verified {
emailCode, err := c.newEmailCode(ctx, filter, codeAlg) emailCode, err := c.newEmailCode(ctx, filter, codeAlg)
@ -460,61 +466,6 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.
return writeModelToHuman(addedHuman), passwordlessCode, nil return writeModelToHuman(addedHuman), passwordlessCode, nil
} }
// Deprecated: use commands.AddUserHuman
func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, orgMemberRoles []string, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator crypto.Generator) (*domain.Human, error) {
if orgID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-GEdf2", "Errors.ResourceOwnerMissing")
}
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
if err != nil {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.Org.DomainPolicy.NotFound")
}
pwPolicy, err := c.getOrgPasswordComplexityPolicy(ctx, orgID)
if err != nil {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.Org.PasswordComplexityPolicy.NotFound")
}
loginPolicy, err := c.getOrgLoginPolicy(ctx, orgID)
if err != nil {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-Dfg3g", "Errors.Org.LoginPolicy.NotFound")
}
// check only if local registration is allowed, the idp will be checked separately
if !loginPolicy.AllowRegister && link == nil {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-SAbr3", "Errors.Org.LoginPolicy.RegistrationNotAllowed")
}
userEvents, registeredHuman, err := c.registerHuman(ctx, orgID, human, link, domainPolicy, pwPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
if err != nil {
return nil, err
}
orgMemberWriteModel := NewOrgMemberWriteModel(orgID, registeredHuman.AggregateID)
orgAgg := OrgAggregateFromWriteModel(&orgMemberWriteModel.WriteModel)
if len(orgMemberRoles) > 0 {
orgMember := &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: orgID,
},
UserID: human.AggregateID,
Roles: orgMemberRoles,
}
memberEvent, err := c.addOrgMember(ctx, orgAgg, orgMemberWriteModel, orgMember)
if err != nil {
return nil, err
}
userEvents = append(userEvents, memberEvent)
}
pushedEvents, err := c.eventstore.Push(ctx, userEvents...)
if err != nil {
return nil, err
}
err = AppendAndReduce(registeredHuman, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToHuman(registeredHuman), nil
}
func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, links []*domain.UserIDPLink, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator crypto.Generator) (events []eventstore.Command, humanWriteModel *HumanWriteModel, passwordlessCodeWriteModel *HumanPasswordlessInitCodeWriteModel, code string, err error) { func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, links []*domain.UserIDPLink, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator crypto.Generator) (events []eventstore.Command, humanWriteModel *HumanWriteModel, passwordlessCodeWriteModel *HumanPasswordlessInitCodeWriteModel, code string, err error) {
if orgID == "" { if orgID == "" {
return nil, nil, nil, "", zerrors.ThrowInvalidArgument(nil, "COMMAND-00p2b", "Errors.Org.Empty") return nil, nil, nil, "", zerrors.ThrowInvalidArgument(nil, "COMMAND-00p2b", "Errors.Org.Empty")
@ -522,7 +473,7 @@ func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.
if err := human.Normalize(); err != nil { if err := human.Normalize(); err != nil {
return nil, nil, nil, "", err return nil, nil, nil, "", err
} }
events, humanWriteModel, err = c.createHuman(ctx, orgID, human, links, false, passwordless, domainPolicy, pwPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator) events, humanWriteModel, err = c.createHuman(ctx, orgID, human, links, passwordless, domainPolicy, pwPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
if err != nil { if err != nil {
return nil, nil, nil, "", err return nil, nil, nil, "", err
} }
@ -537,33 +488,8 @@ func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.
return events, humanWriteModel, passwordlessCodeWriteModel, code, nil return events, humanWriteModel, passwordlessCodeWriteModel, code, nil
} }
func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) { //nolint:gocognit
if human == nil { func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, links []*domain.UserIDPLink, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator crypto.Generator) (events []eventstore.Command, addedHuman *HumanWriteModel, err error) {
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-JKefw", "Errors.User.Invalid")
}
if human.Username = strings.TrimSpace(human.Username); human.Username == "" {
human.Username = string(human.EmailAddress)
}
if orgID == "" {
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-hYsVH", "Errors.Org.Empty")
}
if err := human.Normalize(); err != nil {
return nil, nil, err
}
if link == nil && (human.Password == nil || human.Password.SecretString == "") {
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-X23na", "Errors.User.Password.Empty")
}
if human.Password != nil && human.Password.SecretString != "" {
human.Password.ChangeRequired = false
}
var links []*domain.UserIDPLink
if link != nil {
links = append(links, link)
}
return c.createHuman(ctx, orgID, human, links, true, false, domainPolicy, pwPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
}
func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, links []*domain.UserIDPLink, selfregister, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator crypto.Generator) (events []eventstore.Command, addedHuman *HumanWriteModel, err error) {
if err := human.CheckDomainPolicy(domainPolicy); err != nil { if err := human.CheckDomainPolicy(domainPolicy); err != nil {
return nil, nil, err return nil, nil, err
} }
@ -601,11 +527,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
//TODO: adlerhurst maybe we could simplify the code below //TODO: adlerhurst maybe we could simplify the code below
userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel) userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel)
if selfregister { events = append(events, createAddHumanEvent(ctx, userAgg, human, domainPolicy.UserLoginMustBeDomain))
events = append(events, createRegisterHumanEvent(ctx, userAgg, human, domainPolicy.UserLoginMustBeDomain))
} else {
events = append(events, createAddHumanEvent(ctx, userAgg, human, domainPolicy.UserLoginMustBeDomain))
}
for _, link := range links { for _, link := range links {
event, err := c.addUserIDPLink(ctx, userAgg, link, false) event, err := c.addUserIDPLink(ctx, userAgg, link, false)
@ -620,7 +542,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
events = append(events, user.NewHumanInitialCodeAddedEvent(ctx, userAgg, initCode.Code, initCode.Expiry)) events = append(events, user.NewHumanInitialCodeAddedEvent(ctx, userAgg, initCode.Code, initCode.Expiry, ""))
} else { } else {
if human.Email != nil && human.EmailAddress != "" && human.IsEmailVerified { if human.Email != nil && human.EmailAddress != "" && human.IsEmailVerified {
events = append(events, user.NewHumanEmailVerifiedEvent(ctx, userAgg)) events = append(events, user.NewHumanEmailVerifiedEvent(ctx, userAgg))
@ -629,7 +551,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
events = append(events, user.NewHumanEmailCodeAddedEvent(ctx, userAgg, emailCode.Code, emailCode.Expiry)) events = append(events, user.NewHumanEmailCodeAddedEvent(ctx, userAgg, emailCode.Code, emailCode.Expiry, ""))
} }
} }
@ -699,40 +621,6 @@ func createAddHumanEvent(ctx context.Context, aggregate *eventstore.Aggregate, h
return addEvent return addEvent
} }
func createRegisterHumanEvent(ctx context.Context, aggregate *eventstore.Aggregate, human *domain.Human, userLoginMustBeDomain bool) *user.HumanRegisteredEvent {
addEvent := user.NewHumanRegisteredEvent(
ctx,
aggregate,
human.Username,
human.FirstName,
human.LastName,
human.NickName,
human.DisplayName,
human.PreferredLanguage,
human.Gender,
human.EmailAddress,
userLoginMustBeDomain,
)
if human.Phone != nil {
addEvent.AddPhoneData(human.PhoneNumber)
}
if human.Address != nil {
addEvent.AddAddressData(
human.Country,
human.Locality,
human.PostalCode,
human.Region,
human.StreetAddress)
}
if human.Password != nil {
addEvent.AddPasswordData(human.Password.EncodedSecret, human.Password.ChangeRequired)
}
if human.HashedPassword != "" {
addEvent.AddPasswordData(human.HashedPassword, false)
}
return addEvent
}
func (c *Commands) HumansSignOut(ctx context.Context, agentID string, userIDs []string) error { func (c *Commands) HumansSignOut(ctx context.Context, agentID string, userIDs []string) error {
if agentID == "" { if agentID == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing") return zerrors.ThrowInvalidArgument(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing")
@ -783,3 +671,53 @@ func humanWriteModelByID(ctx context.Context, filter preparation.FilterToQueryRe
err = humanWriteModel.Reduce() err = humanWriteModel.Reduce()
return humanWriteModel, err return humanWriteModel, err
} }
func AddHumanFromDomain(user *domain.Human, metadataList []*domain.Metadata, authRequest *domain.AuthRequest, idp *domain.UserIDPLink) *AddHuman {
addMetadata := make([]*AddMetadataEntry, len(metadataList))
for i, metadata := range metadataList {
addMetadata[i] = &AddMetadataEntry{
Key: metadata.Key,
Value: metadata.Value,
}
}
human := new(AddHuman)
if user.Profile != nil {
human.Username = user.Username
human.FirstName = user.FirstName
human.LastName = user.LastName
human.NickName = user.NickName
human.DisplayName = user.DisplayName
human.PreferredLanguage = user.PreferredLanguage
human.Gender = user.Gender
human.Password = user.Password.SecretString
human.Register = true
human.Metadata = addMetadata
human.UserAgentID = authRequest.AgentID
human.AuthRequestID = authRequest.ID
}
if user.Email != nil {
human.Email = Email{
Address: user.EmailAddress,
Verified: user.IsEmailVerified,
}
}
if user.Phone != nil {
human.Phone = Phone{
Number: user.Phone.PhoneNumber,
Verified: user.Phone.IsPhoneVerified,
}
}
if idp != nil {
human.Links = []*AddLink{
{
IDPID: idp.IDPConfigID,
DisplayName: idp.DisplayName,
IDPExternalID: idp.ExternalUserID,
},
}
}
if human.Username = strings.TrimSpace(human.Username); human.Username == "" {
human.Username = string(human.Email.Address)
}
return human
}

View File

@ -50,7 +50,7 @@ func (c *Commands) ChangeHumanEmail(ctx context.Context, email *domain.Email, em
if err != nil { if err != nil {
return nil, err return nil, err
} }
events = append(events, user.NewHumanEmailCodeAddedEvent(ctx, userAgg, emailCode.Code, emailCode.Expiry)) events = append(events, user.NewHumanEmailCodeAddedEvent(ctx, userAgg, emailCode.Code, emailCode.Expiry, ""))
} }
pushedEvents, err := c.eventstore.Push(ctx, events...) pushedEvents, err := c.eventstore.Push(ctx, events...)
@ -99,7 +99,7 @@ func (c *Commands) VerifyHumanEmail(ctx context.Context, userID, code, resourceo
return nil, zerrors.ThrowInvalidArgument(err, "COMMAND-Gdsgs", "Errors.User.Code.Invalid") return nil, zerrors.ThrowInvalidArgument(err, "COMMAND-Gdsgs", "Errors.User.Code.Invalid")
} }
func (c *Commands) CreateHumanEmailVerificationCode(ctx context.Context, userID, resourceOwner string, emailCodeGenerator crypto.Generator) (*domain.ObjectDetails, error) { func (c *Commands) CreateHumanEmailVerificationCode(ctx context.Context, userID, resourceOwner string, emailCodeGenerator crypto.Generator, authRequestID string) (*domain.ObjectDetails, error) {
if userID == "" { if userID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing")
} }
@ -122,7 +122,10 @@ func (c *Commands) CreateHumanEmailVerificationCode(ctx context.Context, userID,
if err != nil { if err != nil {
return nil, err return nil, err
} }
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanEmailCodeAddedEvent(ctx, userAgg, emailCode.Code, emailCode.Expiry)) if authRequestID == "" {
authRequestID = existingEmail.AuthRequestID
}
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanEmailCodeAddedEvent(ctx, userAgg, emailCode.Code, emailCode.Expiry, authRequestID))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -19,6 +19,7 @@ type HumanEmailWriteModel struct {
Code *crypto.CryptoValue Code *crypto.CryptoValue
CodeCreationDate time.Time CodeCreationDate time.Time
CodeExpiry time.Duration CodeExpiry time.Duration
AuthRequestID string
UserState domain.UserState UserState domain.UserState
} }
@ -53,6 +54,7 @@ func (wm *HumanEmailWriteModel) Reduce() error {
wm.Code = e.Code wm.Code = e.Code
wm.CodeCreationDate = e.CreationDate() wm.CodeCreationDate = e.CreationDate()
wm.CodeExpiry = e.Expiry wm.CodeExpiry = e.Expiry
wm.AuthRequestID = e.AuthRequestID
case *user.HumanEmailVerifiedEvent: case *user.HumanEmailVerifiedEvent:
wm.IsEmailVerified = true wm.IsEmailVerified = true
wm.Code = nil wm.Code = nil

View File

@ -18,7 +18,7 @@ import (
func TestCommandSide_ChangeHumanEmail(t *testing.T) { func TestCommandSide_ChangeHumanEmail(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -39,9 +39,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
{ {
name: "invalid email, invalid argument error", name: "invalid email, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -59,8 +57,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
{ {
name: "user not existing, precondition error", name: "user not existing, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
}, },
@ -81,8 +78,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
{ {
name: "user not initialized, precondition error", name: "user not initialized, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -102,6 +98,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
user.NewHumanInitialCodeAddedEvent(context.Background(), user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate, &user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1, nil, time.Hour*1,
"",
), ),
), ),
), ),
@ -124,8 +121,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
{ {
name: "email not changed, precondition error", name: "email not changed, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -161,8 +157,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
{ {
name: "verified email changed, ok", name: "verified email changed, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -215,8 +210,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
{ {
name: "email verified, ok", name: "email verified, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -265,8 +259,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
{ {
name: "email verified, ok", name: "email verified, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -315,8 +308,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
{ {
name: "email changed with code, ok", name: "email changed with code, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -347,6 +339,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
Crypted: []byte("a"), Crypted: []byte("a"),
}, },
time.Hour*1, time.Hour*1,
"",
), ),
), ),
), ),
@ -376,7 +369,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
} }
got, err := r.ChangeHumanEmail(tt.args.ctx, tt.args.email, tt.args.secretGenerator) got, err := r.ChangeHumanEmail(tt.args.ctx, tt.args.email, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
@ -394,7 +387,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
func TestCommandSide_VerifyHumanEmail(t *testing.T) { func TestCommandSide_VerifyHumanEmail(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -416,9 +409,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
{ {
name: "userid missing, invalid argument error", name: "userid missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -432,9 +423,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
{ {
name: "code missing, invalid argument error", name: "code missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -448,8 +437,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
{ {
name: "user not existing, precondition error", name: "user not existing, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
}, },
@ -466,8 +454,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
{ {
name: "code not existing, precondition error", name: "code not existing, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -499,8 +486,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
{ {
name: "invalid code, invalid argument error", name: "invalid code, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -526,6 +512,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
Crypted: []byte("a"), Crypted: []byte("a"),
}, },
time.Hour*1, time.Hour*1,
"",
), ),
), ),
), ),
@ -550,8 +537,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
{ {
name: "valid code, ok", name: "valid code, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -577,6 +563,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
Crypted: []byte("a"), Crypted: []byte("a"),
}, },
time.Hour*1, time.Hour*1,
"",
), ),
), ),
), ),
@ -604,7 +591,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
} }
got, err := r.VerifyHumanEmail(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.resourceOwner, tt.args.secretGenerator) got, err := r.VerifyHumanEmail(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.resourceOwner, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
@ -622,13 +609,14 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) { func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
userID string userID string
resourceOwner string resourceOwner string
secretGenerator crypto.Generator secretGenerator crypto.Generator
authRequestID string
} }
type res struct { type res struct {
want *domain.ObjectDetails want *domain.ObjectDetails
@ -643,9 +631,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
{ {
name: "userid missing, invalid argument error", name: "userid missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -658,8 +644,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
{ {
name: "user not existing, precondition error", name: "user not existing, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
}, },
@ -675,8 +660,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
{ {
name: "user not initialized, precondition error", name: "user not initialized, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -696,6 +680,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
user.NewHumanInitialCodeAddedEvent(context.Background(), user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate, &user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1, nil, time.Hour*1,
"",
), ),
), ),
), ),
@ -713,8 +698,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
{ {
name: "email already verified, precondition error", name: "email already verified, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -750,8 +734,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
{ {
name: "new code, ok", name: "new code, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -789,6 +772,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
Crypted: []byte("a"), Crypted: []byte("a"),
}, },
time.Hour*1, time.Hour*1,
"",
), ),
), ),
), ),
@ -805,13 +789,72 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
}, },
}, },
}, },
{
name: "new code with authRequestID, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusher(
user.NewHumanEmailChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"email2@test.ch",
),
),
),
expectPush(
user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
"authRequestID",
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
authRequestID: "authRequestID",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
} }
got, err := r.CreateHumanEmailVerificationCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.secretGenerator) got, err := r.CreateHumanEmailVerificationCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.secretGenerator, tt.args.authRequestID)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -827,7 +870,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
func TestCommandSide_EmailVerificationCodeSent(t *testing.T) { func TestCommandSide_EmailVerificationCodeSent(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -846,9 +889,7 @@ func TestCommandSide_EmailVerificationCodeSent(t *testing.T) {
{ {
name: "userid missing, invalid argument error", name: "userid missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -861,8 +902,7 @@ func TestCommandSide_EmailVerificationCodeSent(t *testing.T) {
{ {
name: "user not existing, precondition error", name: "user not existing, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
}, },
@ -878,8 +918,7 @@ func TestCommandSide_EmailVerificationCodeSent(t *testing.T) {
{ {
name: "code sent, ok", name: "code sent, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -925,7 +964,7 @@ func TestCommandSide_EmailVerificationCodeSent(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
} }
err := r.HumanEmailVerificationCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID) err := r.HumanEmailVerificationCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID)
if tt.res.err == nil { if tt.res.err == nil {

View File

@ -13,7 +13,7 @@ import (
) )
// ResendInitialMail resend initial mail and changes email if provided // ResendInitialMail resend initial mail and changes email if provided
func (c *Commands) ResendInitialMail(ctx context.Context, userID string, email domain.EmailAddress, resourceOwner string, initCodeGenerator crypto.Generator) (objectDetails *domain.ObjectDetails, err error) { func (c *Commands) ResendInitialMail(ctx context.Context, userID string, email domain.EmailAddress, resourceOwner string, initCodeGenerator crypto.Generator, authRequestID string) (objectDetails *domain.ObjectDetails, err error) {
if userID == "" { if userID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-2n8vs", "Errors.User.UserIDMissing") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-2n8vs", "Errors.User.UserIDMissing")
} }
@ -38,7 +38,10 @@ func (c *Commands) ResendInitialMail(ctx context.Context, userID string, email d
if err != nil { if err != nil {
return nil, err return nil, err
} }
events = append(events, user.NewHumanInitialCodeAddedEvent(ctx, userAgg, initCode.Code, initCode.Expiry)) if authRequestID == "" {
authRequestID = existingCode.AuthRequestID
}
events = append(events, user.NewHumanInitialCodeAddedEvent(ctx, userAgg, initCode.Code, initCode.Expiry, authRequestID))
pushedEvents, err := c.eventstore.Push(ctx, events...) pushedEvents, err := c.eventstore.Push(ctx, events...)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -19,6 +19,7 @@ type HumanInitCodeWriteModel struct {
Code *crypto.CryptoValue Code *crypto.CryptoValue
CodeCreationDate time.Time CodeCreationDate time.Time
CodeExpiry time.Duration CodeExpiry time.Duration
AuthRequestID string
UserState domain.UserState UserState domain.UserState
} }
@ -50,6 +51,7 @@ func (wm *HumanInitCodeWriteModel) Reduce() error {
wm.Code = e.Code wm.Code = e.Code
wm.CodeCreationDate = e.CreationDate() wm.CodeCreationDate = e.CreationDate()
wm.CodeExpiry = e.Expiry wm.CodeExpiry = e.Expiry
wm.AuthRequestID = e.AuthRequestID
wm.UserState = domain.UserStateInitial wm.UserState = domain.UserStateInitial
case *user.HumanInitializedCheckSucceededEvent: case *user.HumanInitializedCheckSucceededEvent:
wm.Code = nil wm.Code = nil

View File

@ -18,7 +18,7 @@ import (
func TestCommandSide_ResendInitialMail(t *testing.T) { func TestCommandSide_ResendInitialMail(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -26,6 +26,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
email string email string
resourceOwner string resourceOwner string
secretGenerator crypto.Generator secretGenerator crypto.Generator
authRequestID string
} }
type res struct { type res struct {
want *domain.ObjectDetails want *domain.ObjectDetails
@ -40,9 +41,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
{ {
name: "userid missing, invalid argument error", name: "userid missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -55,8 +54,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
{ {
name: "user not existing, precondition error", name: "user not existing, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
}, },
@ -72,8 +70,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
{ {
name: "user not initialized, precondition error", name: "user not initialized, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -107,8 +104,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
{ {
name: "new code email not changed, ok", name: "new code email not changed, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -128,6 +124,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
user.NewHumanInitialCodeAddedEvent(context.Background(), user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate, &user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1, nil, time.Hour*1,
"",
), ),
), ),
), ),
@ -141,6 +138,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
Crypted: []byte("a"), Crypted: []byte("a"),
}, },
time.Hour*1, time.Hour*1,
"",
), ),
), ),
), ),
@ -159,10 +157,9 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
}, },
}, },
{ {
name: "new code, ok", name: "new code email not changed with authRequestID, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -182,6 +179,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
user.NewHumanInitialCodeAddedEvent(context.Background(), user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate, &user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1, nil, time.Hour*1,
"authRequestID",
), ),
), ),
), ),
@ -195,6 +193,63 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
Crypted: []byte("a"), Crypted: []byte("a"),
}, },
time.Hour*1, time.Hour*1,
"authRequestID",
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
email: "email@test.ch",
secretGenerator: GetMockSecretGenerator(t),
authRequestID: "authRequestID",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "new code, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1,
"",
),
),
),
expectPush(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
"",
), ),
), ),
), ),
@ -212,10 +267,9 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
}, },
}, },
{ {
name: "new code with change email, ok", name: "new code with authRequestID, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -235,6 +289,62 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
user.NewHumanInitialCodeAddedEvent(context.Background(), user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate, &user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1, nil, time.Hour*1,
"authRequestID",
),
),
),
expectPush(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
"authRequestID",
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
authRequestID: "authRequestID",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "new code with change email, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1,
"",
), ),
), ),
), ),
@ -252,6 +362,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
Crypted: []byte("a"), Crypted: []byte("a"),
}, },
time.Hour*1, time.Hour*1,
"",
), ),
), ),
), ),
@ -273,9 +384,9 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
} }
got, err := r.ResendInitialMail(tt.args.ctx, tt.args.userID, domain.EmailAddress(tt.args.email), tt.args.resourceOwner, tt.args.secretGenerator) got, err := r.ResendInitialMail(tt.args.ctx, tt.args.userID, domain.EmailAddress(tt.args.email), tt.args.resourceOwner, tt.args.secretGenerator, tt.args.authRequestID)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -291,7 +402,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
func TestCommandSide_VerifyInitCode(t *testing.T) { func TestCommandSide_VerifyInitCode(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
userPasswordHasher *crypto.Hasher userPasswordHasher *crypto.Hasher
} }
type args struct { type args struct {
@ -316,9 +427,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
{ {
name: "userid missing, invalid argument error", name: "userid missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -332,9 +441,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
{ {
name: "code missing, invalid argument error", name: "code missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -348,8 +455,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
{ {
name: "user not existing, precondition error", name: "user not existing, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
}, },
@ -366,8 +472,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
{ {
name: "code not existing, precondition error", name: "code not existing, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -399,8 +504,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
{ {
name: "invalid code, invalid argument error", name: "invalid code, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -426,6 +530,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
Crypted: []byte("a"), Crypted: []byte("a"),
}, },
time.Hour*1, time.Hour*1,
"",
), ),
), ),
), ),
@ -450,8 +555,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
{ {
name: "valid code, ok", name: "valid code, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -477,6 +581,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
Crypted: []byte("a"), Crypted: []byte("a"),
}, },
time.Hour*1, time.Hour*1,
"",
), ),
), ),
), ),
@ -506,8 +611,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
{ {
name: "valid code with password, ok", name: "valid code with password, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -536,6 +640,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
Crypted: []byte("a"), Crypted: []byte("a"),
}, },
time.Hour*1, time.Hour*1,
"",
), ),
), ),
), ),
@ -582,8 +687,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
{ {
name: "valid code with password and userAgentID, ok", name: "valid code with password and userAgentID, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -612,6 +716,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
Crypted: []byte("a"), Crypted: []byte("a"),
}, },
time.Hour*1, time.Hour*1,
"",
), ),
), ),
), ),
@ -660,7 +765,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher, userPasswordHasher: tt.fields.userPasswordHasher,
} }
err := r.HumanVerifyInitCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.code, tt.args.password, tt.args.userAgentID, tt.args.secretGenerator) err := r.HumanVerifyInitCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.code, tt.args.password, tt.args.userAgentID, tt.args.secretGenerator)
@ -676,7 +781,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
func TestCommandSide_InitCodeSent(t *testing.T) { func TestCommandSide_InitCodeSent(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -695,9 +800,7 @@ func TestCommandSide_InitCodeSent(t *testing.T) {
{ {
name: "userid missing, invalid argument error", name: "userid missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -710,8 +813,7 @@ func TestCommandSide_InitCodeSent(t *testing.T) {
{ {
name: "user not existing, precondition error", name: "user not existing, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
}, },
@ -727,8 +829,7 @@ func TestCommandSide_InitCodeSent(t *testing.T) {
{ {
name: "code sent, ok", name: "code sent, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -763,7 +864,7 @@ func TestCommandSide_InitCodeSent(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
} }
err := r.HumanInitCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID) err := r.HumanInitCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID)
if tt.res.err == nil { if tt.res.err == nil {

View File

@ -165,7 +165,7 @@ func (c *Commands) canUpdatePassword(ctx context.Context, newPassword string, re
} }
// RequestSetPassword generate and send out new code to change password for a specific user // RequestSetPassword generate and send out new code to change password for a specific user
func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner string, notifyType domain.NotificationType, passwordVerificationCode crypto.Generator) (objectDetails *domain.ObjectDetails, err error) { func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner string, notifyType domain.NotificationType, passwordVerificationCode crypto.Generator, authRequestID string) (objectDetails *domain.ObjectDetails, err error) {
if userID == "" { if userID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-M00oL", "Errors.User.UserIDMissing") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-M00oL", "Errors.User.UserIDMissing")
} }
@ -185,7 +185,7 @@ func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner
if err != nil { if err != nil {
return nil, err return nil, err
} }
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanPasswordCodeAddedEvent(ctx, userAgg, passwordCode.Code, passwordCode.Expiry, notifyType)) pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanPasswordCodeAddedEvent(ctx, userAgg, passwordCode.Code, passwordCode.Expiry, notifyType, authRequestID))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -22,7 +22,7 @@ import (
func TestCommandSide_SetOneTimePassword(t *testing.T) { func TestCommandSide_SetOneTimePassword(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
userPasswordHasher *crypto.Hasher userPasswordHasher *crypto.Hasher
checkPermission domain.PermissionCheck checkPermission domain.PermissionCheck
} }
@ -46,9 +46,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
{ {
name: "userid missing, invalid argument error", name: "userid missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -61,8 +59,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
{ {
name: "user not existing, precondition error", name: "user not existing, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
}, },
@ -78,8 +75,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
{ {
name: "missing permission, error", name: "missing permission, error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -121,8 +117,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
{ {
name: "change password onetime, ok", name: "change password onetime, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -184,8 +179,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
{ {
name: "change password no one time, ok", name: "change password no one time, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -248,7 +242,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher, userPasswordHasher: tt.fields.userPasswordHasher,
checkPermission: tt.fields.checkPermission, checkPermission: tt.fields.checkPermission,
} }
@ -268,7 +262,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) { func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
userEncryption crypto.EncryptionAlgorithm userEncryption crypto.EncryptionAlgorithm
userPasswordHasher *crypto.Hasher userPasswordHasher *crypto.Hasher
} }
@ -293,9 +287,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
{ {
name: "userid missing, invalid argument error", name: "userid missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -308,9 +300,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
{ {
name: "password missing, invalid argument error", name: "password missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -324,8 +314,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
{ {
name: "user not existing, precondition error", name: "user not existing, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
}, },
@ -342,8 +331,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
{ {
name: "code not existing, precondition error", name: "code not existing, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -376,8 +364,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
{ {
name: "invalid code, invalid argument error", name: "invalid code, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -404,6 +391,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
}, },
time.Hour*1, time.Hour*1,
domain.NotificationTypeEmail, domain.NotificationTypeEmail,
"",
), ),
), ),
), ),
@ -424,8 +412,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
{ {
name: "set password, ok", name: "set password, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -457,6 +444,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
}, },
time.Hour*1, time.Hour*1,
domain.NotificationTypeEmail, domain.NotificationTypeEmail,
"",
), ),
), ),
), ),
@ -500,8 +488,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
{ {
name: "set password with userAgentID, ok", name: "set password with userAgentID, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -533,6 +520,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
}, },
time.Hour*1, time.Hour*1,
domain.NotificationTypeEmail, domain.NotificationTypeEmail,
"",
), ),
), ),
), ),
@ -578,7 +566,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher, userPasswordHasher: tt.fields.userPasswordHasher,
userEncryption: tt.fields.userEncryption, userEncryption: tt.fields.userEncryption,
} }
@ -915,7 +903,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
func TestCommandSide_RequestSetPassword(t *testing.T) { func TestCommandSide_RequestSetPassword(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -923,6 +911,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
resourceOwner string resourceOwner string
notifyType domain.NotificationType notifyType domain.NotificationType
secretGenerator crypto.Generator secretGenerator crypto.Generator
authRequestID string
} }
type res struct { type res struct {
want *domain.ObjectDetails want *domain.ObjectDetails
@ -937,9 +926,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
{ {
name: "userid missing, invalid argument error", name: "userid missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -952,8 +939,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
{ {
name: "user not existing, precondition error", name: "user not existing, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
}, },
@ -969,8 +955,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
{ {
name: "user initial, precondition error", name: "user initial, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -990,6 +975,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
user.NewHumanInitialCodeAddedEvent(context.Background(), user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate, &user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1, nil, time.Hour*1,
"",
), ),
), ),
eventFromEventPusher( eventFromEventPusher(
@ -1018,8 +1004,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
{ {
name: "new code, ok", name: "new code, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -1055,6 +1040,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
}, },
time.Hour*1, time.Hour*1,
domain.NotificationTypeEmail, domain.NotificationTypeEmail,
"",
), ),
), ),
), ),
@ -1071,13 +1057,70 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
}, },
}, },
}, },
{
name: "new code with authRequestID, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusher(
user.NewHumanInitializedCheckSucceededEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate)),
),
expectPush(
user.NewHumanPasswordCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
domain.NotificationTypeEmail,
"authRequestID",
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
authRequestID: "authRequestID",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
} }
got, err := r.RequestSetPassword(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.notifyType, tt.args.secretGenerator) got, err := r.RequestSetPassword(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.notifyType, tt.args.secretGenerator, tt.args.authRequestID)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -1093,7 +1136,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
func TestCommandSide_PasswordCodeSent(t *testing.T) { func TestCommandSide_PasswordCodeSent(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -1112,9 +1155,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
{ {
name: "userid missing, invalid argument error", name: "userid missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -1127,8 +1168,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
{ {
name: "user not existing, precondition error", name: "user not existing, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
}, },
@ -1144,8 +1184,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
{ {
name: "code sent, ok", name: "code sent, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -1186,7 +1225,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
} }
err := r.PasswordCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID) err := r.PasswordCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID)
if tt.res.err == nil { if tt.res.err == nil {
@ -1201,7 +1240,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
func TestCommandSide_CheckPassword(t *testing.T) { func TestCommandSide_CheckPassword(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
userPasswordHasher *crypto.Hasher userPasswordHasher *crypto.Hasher
} }
type args struct { type args struct {
@ -1224,9 +1263,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{ {
name: "userid missing, invalid argument error", name: "userid missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -1240,9 +1277,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{ {
name: "password missing, invalid argument error", name: "password missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -1256,8 +1291,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{ {
name: "login policy not found, precondition error", name: "login policy not found, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
expectFilter(), expectFilter(),
), ),
@ -1275,8 +1309,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{ {
name: "login policy login password not allowed, precondition error", name: "login policy login password not allowed, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(), org.NewLoginPolicyAddedEvent(context.Background(),
@ -1316,8 +1349,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{ {
name: "user not existing, precondition error", name: "user not existing, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(), org.NewLoginPolicyAddedEvent(context.Background(),
@ -1358,8 +1390,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{ {
name: "user locked, precondition error", name: "user locked, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(), org.NewLoginPolicyAddedEvent(context.Background(),
@ -1420,8 +1451,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{ {
name: "existing password empty, precondition error", name: "existing password empty, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(), org.NewLoginPolicyAddedEvent(context.Background(),
@ -1478,8 +1508,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{ {
name: "password not matching lockout policy not relevant, precondition error", name: "password not matching lockout policy not relevant, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(), org.NewLoginPolicyAddedEvent(context.Background(),
@ -1562,8 +1591,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{ {
name: "password not matching, max password attempts reached - user locked, precondition error", name: "password not matching, max password attempts reached - user locked, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(), org.NewLoginPolicyAddedEvent(context.Background(),
@ -1653,8 +1681,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{ {
name: "check password, ok", name: "check password, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(), org.NewLoginPolicyAddedEvent(context.Background(),
@ -1734,8 +1761,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{ {
name: "check password, ok, updated hash", name: "check password, ok, updated hash",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(), org.NewLoginPolicyAddedEvent(context.Background(),
@ -1820,8 +1846,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{ {
name: "check password ok, locked in the mean time", name: "check password ok, locked in the mean time",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(), org.NewLoginPolicyAddedEvent(context.Background(),
@ -1900,8 +1925,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
{ {
name: "regression test old version event", name: "regression test old version event",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(), org.NewLoginPolicyAddedEvent(context.Background(),
@ -1996,7 +2020,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher, userPasswordHasher: tt.fields.userPasswordHasher,
} }
err := r.HumanCheckPassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.password, tt.args.authReq, tt.args.lockoutPolicy) err := r.HumanCheckPassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.password, tt.args.authReq, tt.args.lockoutPolicy)

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/crypto"
"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/v1/models" "github.com/zitadel/zitadel/internal/eventstore/v1/models"
@ -78,6 +79,12 @@ func (key *MachineKey) valid() (err error) {
if err := key.content(); err != nil { if err := key.content(); err != nil {
return err return err
} }
// If a key is supplied, it should be a valid public key
if len(key.PublicKey) > 0 {
if _, err := crypto.BytesToPublicKey(key.PublicKey); err != nil {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-5F3h1", "Errors.User.Machine.Key.Invalid")
}
}
key.ExpirationDate, err = domain.ValidateExpirationDate(key.ExpirationDate) key.ExpirationDate, err = domain.ValidateExpirationDate(key.ExpirationDate)
return err return err
} }

View File

@ -18,6 +18,16 @@ import (
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
const fakePubkey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp4qNBuUu/HekF2E5bOtA
oEL76zS0NQdZL3ByEJ3hZplJhE30ITPIOLW3+uaMMM+obl/LLapwG2vdhvutQtx/
FOLJmXysbG3RL9zjXDBT5IE+nGFC7ctsi5FGbHQbAm45E3HHCSk7gfmTy9hxyk1K
GsyU8BDeOWasJO6aeXqpOnRM8vw/fY+6mHVC9CxcIroSfrIabFGe/mP6qpBGeFSn
APymBc/8lca4JaPv2/u/rBhnaAHZiUuCS1+MonWelOb+MSfq48VgtpiaYIVY9szI
esorA6EJ9pO17ROEUpX5wP5Oir+yGJU27jSvLCjvK6fOFX+OwUM9L8047JKoo+Nf
PwIDAQAB
-----END PUBLIC KEY-----`
func TestCommands_AddMachineKey(t *testing.T) { func TestCommands_AddMachineKey(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
@ -145,7 +155,7 @@ func TestCommands_AddMachineKey(t *testing.T) {
"key1", "key1",
domain.AuthNKeyTypeJSON, domain.AuthNKeyTypeJSON,
time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC), time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC),
[]byte("public"), []byte(fakePubkey),
), ),
), ),
), ),
@ -161,14 +171,14 @@ func TestCommands_AddMachineKey(t *testing.T) {
}, },
Type: domain.AuthNKeyTypeJSON, Type: domain.AuthNKeyTypeJSON,
ExpirationDate: time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC), ExpirationDate: time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC),
PublicKey: []byte("public"), PublicKey: []byte(fakePubkey),
}, },
}, },
res{ res{
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
ResourceOwner: "org1", ResourceOwner: "org1",
}, },
key: true, key: false,
}, },
}, },
{ {
@ -194,7 +204,7 @@ func TestCommands_AddMachineKey(t *testing.T) {
"key1", "key1",
domain.AuthNKeyTypeJSON, domain.AuthNKeyTypeJSON,
time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC), time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC),
[]byte("public"), []byte(fakePubkey),
), ),
), ),
), ),
@ -210,14 +220,35 @@ func TestCommands_AddMachineKey(t *testing.T) {
KeyID: "key1", KeyID: "key1",
Type: domain.AuthNKeyTypeJSON, Type: domain.AuthNKeyTypeJSON,
ExpirationDate: time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC), ExpirationDate: time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC),
PublicKey: []byte("public"), PublicKey: []byte(fakePubkey),
}, },
}, },
res{ res{
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
ResourceOwner: "org1", ResourceOwner: "org1",
}, },
key: true, key: false,
},
},
{
"key added with invalid public key",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
key: &MachineKey{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
KeyID: "key1",
Type: domain.AuthNKeyTypeJSON,
PublicKey: []byte("incorrect"),
},
},
res{
err: zerrors.IsErrorInvalidArgument,
}, },
}, },
} }
@ -237,9 +268,8 @@ func TestCommands_AddMachineKey(t *testing.T) {
} }
if tt.res.err == nil { if tt.res.err == nil {
assert.Equal(t, tt.res.want, got) assert.Equal(t, tt.res.want, got)
if tt.res.key { receivedKey := len(tt.args.key.PrivateKey) > 0
assert.NotEqual(t, "", tt.args.key.PrivateKey) assert.Equal(t, tt.res.key, receivedKey)
}
} }
}) })
} }

View File

@ -1797,6 +1797,7 @@ func TestExistsUser(t *testing.T) {
domain.GenderFemale, domain.GenderFemale,
"support@zitadel.com", "support@zitadel.com",
true, true,
"userAgentID",
), ),
}, nil }, nil
}, },

View File

@ -1838,7 +1838,7 @@ func TestCommands_verifyUserEmailWithGenerator(t *testing.T) {
func TestCommands_NewUserEmailEvents(t *testing.T) { func TestCommands_NewUserEmailEvents(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
} }
type args struct { type args struct {
userID string userID string
@ -1852,7 +1852,7 @@ func TestCommands_NewUserEmailEvents(t *testing.T) {
{ {
name: "missing userID", name: "missing userID",
fields: fields{ fields: fields{
eventstore: eventstoreExpect(t), eventstore: expectEventstore(),
}, },
args: args{ args: args{
userID: "", userID: "",
@ -1862,7 +1862,7 @@ func TestCommands_NewUserEmailEvents(t *testing.T) {
{ {
name: "not found", name: "not found",
fields: fields{ fields: fields{
eventstore: eventstoreExpect(t, expectFilter()), eventstore: expectEventstore(expectFilter()),
}, },
args: args{ args: args{
userID: "user1", userID: "user1",
@ -1872,8 +1872,7 @@ func TestCommands_NewUserEmailEvents(t *testing.T) {
{ {
name: "user not initialized", name: "user not initialized",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -1893,6 +1892,7 @@ func TestCommands_NewUserEmailEvents(t *testing.T) {
user.NewHumanInitialCodeAddedEvent(context.Background(), user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate, &user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1, nil, time.Hour*1,
"",
), ),
), ),
), ),
@ -1907,7 +1907,7 @@ func TestCommands_NewUserEmailEvents(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := &Commands{ c := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
} }
_, err := c.NewUserEmailEvents(context.Background(), tt.args.userID) _, err := c.NewUserEmailEvents(context.Background(), tt.args.userID)
require.ErrorIs(t, err, tt.wantErr) require.ErrorIs(t, err, tt.wantErr)

View File

@ -131,8 +131,10 @@ func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-7yiox1isql", "Errors.User.AlreadyExisting") return zerrors.ThrowPreconditionFailed(nil, "COMMAND-7yiox1isql", "Errors.User.AlreadyExisting")
} }
// check for permission to create user on resourceOwner // check for permission to create user on resourceOwner
if err := c.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, human.ID); err != nil { if !human.Register {
return err if err := c.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, human.ID); err != nil {
return err
}
} }
// add resourceowner for the events with the aggregate // add resourceowner for the events with the aggregate
existingHuman.ResourceOwner = resourceOwner existingHuman.ResourceOwner = resourceOwner
@ -159,6 +161,7 @@ func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human
human.Gender, human.Gender,
human.Email.Address, human.Email.Address,
domainPolicy.UserLoginMustBeDomain, domainPolicy.UserLoginMustBeDomain,
human.UserAgentID,
) )
} else { } else {
createCmd = user.NewHumanAddedEvent( createCmd = user.NewHumanAddedEvent(

View File

@ -232,6 +232,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
domain.GenderUnspecified, domain.GenderUnspecified,
"email@test.ch", "email@test.ch",
true, true,
"userAgentID",
), ),
user.NewHumanInitialCodeAddedEvent(context.Background(), user.NewHumanInitialCodeAddedEvent(context.Background(),
&userAgg.Aggregate, &userAgg.Aggregate,
@ -242,6 +243,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
Crypted: []byte("userinit"), Crypted: []byte("userinit"),
}, },
time.Hour*1, time.Hour*1,
"authRequestID",
), ),
), ),
), ),
@ -261,6 +263,8 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
}, },
PreferredLanguage: language.English, PreferredLanguage: language.English,
Register: true, Register: true,
UserAgentID: "userAgentID",
AuthRequestID: "authRequestID",
}, },
secretGenerator: GetMockSecretGenerator(t), secretGenerator: GetMockSecretGenerator(t),
allowInitMail: true, allowInitMail: true,
@ -344,6 +348,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
Crypted: []byte("userinit"), Crypted: []byte("userinit"),
}, },
time.Hour*1, time.Hour*1,
"",
), ),
), ),
), ),
@ -414,6 +419,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
Crypted: []byte("userinit"), Crypted: []byte("userinit"),
}, },
1*time.Hour, 1*time.Hour,
"",
), ),
), ),
), ),
@ -1031,6 +1037,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
Crypted: []byte("userinit"), Crypted: []byte("userinit"),
}, },
1*time.Hour, 1*time.Hour,
"",
), ),
user.NewHumanPhoneVerifiedEvent( user.NewHumanPhoneVerifiedEvent(
context.Background(), context.Background(),
@ -1174,6 +1181,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
Crypted: []byte("userinit"), Crypted: []byte("userinit"),
}, },
1*time.Hour, 1*time.Hour,
"",
), ),
user.NewMetadataSetEvent( user.NewMetadataSetEvent(
context.Background(), context.Background(),
@ -1993,6 +2001,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
user.NewHumanInitialCodeAddedEvent(context.Background(), user.NewHumanInitialCodeAddedEvent(context.Background(),
&userAgg.Aggregate, &userAgg.Aggregate,
nil, time.Hour*1, nil, time.Hour*1,
"",
), ),
), ),
), ),

View File

@ -167,6 +167,7 @@ func TestCommandSide_userExistsWriteModel(t *testing.T) {
Crypted: []byte("a"), Crypted: []byte("a"),
}, },
time.Hour*1, time.Hour*1,
"authRequestID",
), ),
), ),
), ),
@ -225,6 +226,7 @@ func TestCommandSide_userExistsWriteModel(t *testing.T) {
Crypted: []byte("a"), Crypted: []byte("a"),
}, },
time.Hour*1, time.Hour*1,
"authRequestID",
), ),
), ),
eventFromEventPusher( eventFromEventPusher(
@ -280,6 +282,7 @@ func TestCommandSide_userExistsWriteModel(t *testing.T) {
Crypted: []byte("a"), Crypted: []byte("a"),
}, },
time.Hour*1, time.Hour*1,
"authRequestID",
), ),
), ),
eventFromEventPusher( eventFromEventPusher(

View File

@ -10,6 +10,7 @@ import (
"github.com/muhlemmer/gu" "github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
@ -69,7 +70,7 @@ func TestCommands_RequestPasswordReset(t *testing.T) {
), ),
eventFromEventPusher( eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate, user.NewHumanInitialCodeAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second), &crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second, ""),
), ),
), ),
), ),
@ -167,7 +168,7 @@ func TestCommands_RequestPasswordResetReturnCode(t *testing.T) {
), ),
eventFromEventPusher( eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate, user.NewHumanInitialCodeAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second), &crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second, ""),
), ),
), ),
), ),
@ -279,7 +280,7 @@ func TestCommands_RequestPasswordResetURLTemplate(t *testing.T) {
), ),
eventFromEventPusher( eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate, user.NewHumanInitialCodeAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second), &crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second, ""),
), ),
), ),
), ),
@ -390,7 +391,7 @@ func TestCommands_requestPasswordReset(t *testing.T) {
), ),
eventFromEventPusher( eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate, user.NewHumanInitialCodeAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second), &crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second, ""),
), ),
), ),
), ),

View File

@ -18,7 +18,7 @@ import (
func TestCommandSide_LockUserV2(t *testing.T) { func TestCommandSide_LockUserV2(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck checkPermission domain.PermissionCheck
} }
type ( type (
@ -40,9 +40,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
{ {
name: "userid missing, invalid argument error", name: "userid missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
}, },
args: args{ args: args{
@ -58,8 +56,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
{ {
name: "user not existing, not found error", name: "user not existing, not found error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
@ -77,8 +74,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
{ {
name: "user already locked, precondition error", name: "user already locked, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -116,8 +112,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
{ {
name: "user already locked, precondition error", name: "user already locked, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(), user.NewMachineAddedEvent(context.Background(),
@ -151,8 +146,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
{ {
name: "lock user, ok", name: "lock user, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -190,8 +184,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
{ {
name: "lock user, no permission", name: "lock user, no permission",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -224,8 +217,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
{ {
name: "lock user machine, ok", name: "lock user machine, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(), user.NewMachineAddedEvent(context.Background(),
@ -260,7 +252,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission, checkPermission: tt.fields.checkPermission,
} }
got, err := r.LockUserV2(tt.args.ctx, tt.args.userID) got, err := r.LockUserV2(tt.args.ctx, tt.args.userID)
@ -279,7 +271,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
func TestCommandSide_UnlockUserV2(t *testing.T) { func TestCommandSide_UnlockUserV2(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck checkPermission domain.PermissionCheck
} }
type ( type (
@ -301,9 +293,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
{ {
name: "userid missing, invalid argument error", name: "userid missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
}, },
args: args{ args: args{
@ -319,8 +309,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
{ {
name: "user not existing, not found error", name: "user not existing, not found error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
@ -338,8 +327,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
{ {
name: "user already active, precondition error", name: "user already active, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -372,8 +360,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
{ {
name: "user already active, precondition error", name: "user already active, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
user.NewMachineAddedEvent(context.Background(), user.NewMachineAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate, &user.NewAggregate("user1", "org1").Aggregate,
@ -400,8 +387,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
{ {
name: "unlock user, ok", name: "unlock user, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -443,8 +429,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
{ {
name: "unlock user, no permission", name: "unlock user, no permission",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -481,8 +466,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
{ {
name: "unlock user machine, ok", name: "unlock user machine, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(), user.NewMachineAddedEvent(context.Background(),
@ -521,7 +505,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission, checkPermission: tt.fields.checkPermission,
} }
got, err := r.UnlockUserV2(tt.args.ctx, tt.args.userID) got, err := r.UnlockUserV2(tt.args.ctx, tt.args.userID)
@ -540,7 +524,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
func TestCommandSide_DeactivateUserV2(t *testing.T) { func TestCommandSide_DeactivateUserV2(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck checkPermission domain.PermissionCheck
} }
type ( type (
@ -562,9 +546,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
{ {
name: "userid missing, invalid argument error", name: "userid missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
}, },
args: args{ args: args{
@ -580,8 +562,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
{ {
name: "user not existing, not found error", name: "user not existing, not found error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
@ -599,8 +580,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
{ {
name: "user initial, precondition error", name: "user initial, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -620,6 +600,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
user.NewHumanInitialCodeAddedEvent(context.Background(), user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate, &user.NewAggregate("user1", "org1").Aggregate,
nil, time.Hour*1, nil, time.Hour*1,
"",
), ),
), ),
), ),
@ -639,8 +620,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
{ {
name: "user already inactive, precondition error", name: "user already inactive, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -678,8 +658,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
{ {
name: "deactivate user, ok", name: "deactivate user, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -722,8 +701,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
{ {
name: "deactivate user, no permission", name: "deactivate user, no permission",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -761,8 +739,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
{ {
name: "user machine already inactive, precondition error", name: "user machine already inactive, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(), user.NewMachineAddedEvent(context.Background(),
@ -796,8 +773,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
{ {
name: "deactivate user machine, ok", name: "deactivate user machine, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(), user.NewMachineAddedEvent(context.Background(),
@ -832,7 +808,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission, checkPermission: tt.fields.checkPermission,
} }
got, err := r.DeactivateUserV2(tt.args.ctx, tt.args.userID) got, err := r.DeactivateUserV2(tt.args.ctx, tt.args.userID)
@ -851,7 +827,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
func TestCommandSide_ReactivateUserV2(t *testing.T) { func TestCommandSide_ReactivateUserV2(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck checkPermission domain.PermissionCheck
} }
type ( type (
@ -873,9 +849,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
{ {
name: "userid missing, invalid argument error", name: "userid missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
}, },
args: args{ args: args{
@ -891,8 +865,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
{ {
name: "user not existing, not found error", name: "user not existing, not found error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
@ -910,8 +883,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
{ {
name: "user already active, precondition error", name: "user already active, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -944,8 +916,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
{ {
name: "user machine already active, precondition error", name: "user machine already active, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(), user.NewMachineAddedEvent(context.Background(),
@ -974,8 +945,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
{ {
name: "reactivate user, ok", name: "reactivate user, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -1017,8 +987,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
{ {
name: "reactivate user, no permission", name: "reactivate user, no permission",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -1055,8 +1024,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
{ {
name: "reactivate user machine, ok", name: "reactivate user machine, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(), user.NewMachineAddedEvent(context.Background(),
@ -1095,7 +1063,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission, checkPermission: tt.fields.checkPermission,
} }
got, err := r.ReactivateUserV2(tt.args.ctx, tt.args.userID) got, err := r.ReactivateUserV2(tt.args.ctx, tt.args.userID)
@ -1114,7 +1082,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
func TestCommandSide_RemoveUserV2(t *testing.T) { func TestCommandSide_RemoveUserV2(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck checkPermission domain.PermissionCheck
} }
type ( type (
@ -1138,9 +1106,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
{ {
name: "userid missing, invalid argument error", name: "userid missing, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
}, },
args: args{ args: args{
@ -1156,8 +1122,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
{ {
name: "user not existing, not found error", name: "user not existing, not found error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
@ -1175,8 +1140,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
{ {
name: "user removed, notfound error", name: "user removed, notfound error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -1217,8 +1181,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
{ {
name: "remove user, ok", name: "remove user, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -1269,8 +1232,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
{ {
name: "remove user, no permission", name: "remove user, no permission",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -1308,8 +1270,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
{ {
name: "user machine already removed, notfound error", name: "user machine already removed, notfound error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(), user.NewMachineAddedEvent(context.Background(),
@ -1346,8 +1307,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
{ {
name: "remove user machine, ok", name: "remove user machine, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(), user.NewMachineAddedEvent(context.Background(),
@ -1395,7 +1355,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission, checkPermission: tt.fields.checkPermission,
} }
got, err := r.RemoveUserV2(tt.args.ctx, tt.args.userID, tt.args.cascadingMemberships, tt.args.grantIDs...) got, err := r.RemoveUserV2(tt.args.ctx, tt.args.userID, tt.args.cascadingMemberships, tt.args.grantIDs...)

View File

@ -169,7 +169,7 @@ func (u *userNotifier) reduceInitCodeAdded(event eventstore.Event) (*handler.Sta
return err return err
} }
err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e). err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e).
SendUserInitCode(ctx, notifyUser, code) SendUserInitCode(ctx, notifyUser, code, e.AuthRequestID)
if err != nil { if err != nil {
return err return err
} }
@ -226,7 +226,7 @@ func (u *userNotifier) reduceEmailCodeAdded(event eventstore.Event) (*handler.St
return err return err
} }
err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e). err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e).
SendEmailVerificationCode(ctx, notifyUser, code, e.URLTemplate) SendEmailVerificationCode(ctx, notifyUser, code, e.URLTemplate, e.AuthRequestID)
if err != nil { if err != nil {
return err return err
} }
@ -285,7 +285,7 @@ func (u *userNotifier) reducePasswordCodeAdded(event eventstore.Event) (*handler
if e.NotificationType == domain.NotificationTypeSms { if e.NotificationType == domain.NotificationTypeSms {
notify = types.SendSMSTwilio(ctx, u.channels, translator, notifyUser, colors, e) notify = types.SendSMSTwilio(ctx, u.channels, translator, notifyUser, colors, e)
} }
err = notify.SendPasswordCode(ctx, notifyUser, code, e.URLTemplate) err = notify.SendPasswordCode(ctx, notifyUser, code, e.URLTemplate, e.AuthRequestID)
if err != nil { if err != nil {
return err return err
} }

View File

@ -45,6 +45,7 @@ const (
externalSecure = false externalSecure = false
externalProtocol = "http" externalProtocol = "http"
defaultOTPEmailTemplate = "/otp/verify?loginName={{.LoginName}}&code={{.Code}}" defaultOTPEmailTemplate = "/otp/verify?loginName={{.LoginName}}&code={{.Code}}"
authRequestID = "authRequestID"
) )
func Test_userNotifier_reduceInitCodeAdded(t *testing.T) { func Test_userNotifier_reduceInitCodeAdded(t *testing.T) {
@ -128,7 +129,7 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) {
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}" givenTemplate := "{{.URL}}"
testCode := "testcode" testCode := "testcode"
expectContent := fmt.Sprintf("%s/ui/login/user/init?userID=%s&loginname=%s&code=%s&orgID=%s&passwordset=%t", eventOrigin, userID, preferredLoginName, testCode, orgID, false) expectContent := fmt.Sprintf("%s/ui/login/user/init?authRequestID=%s&code=%s&loginname=%s&orgID=%s&passwordset=%t&userID=%s", eventOrigin, "", testCode, preferredLoginName, orgID, false, userID)
w.message = messages.Email{ w.message = messages.Email{
Recipients: []string{lastEmail}, Recipients: []string{lastEmail},
Subject: expectMailSubject, Subject: expectMailSubject,
@ -162,7 +163,7 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) {
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}" givenTemplate := "{{.URL}}"
testCode := "testcode" testCode := "testcode"
expectContent := fmt.Sprintf("%s://%s:%d/ui/login/user/init?userID=%s&loginname=%s&code=%s&orgID=%s&passwordset=%t", externalProtocol, instancePrimaryDomain, externalPort, userID, preferredLoginName, testCode, orgID, false) expectContent := fmt.Sprintf("%s://%s:%d/ui/login/user/init?authRequestID=%s&code=%s&loginname=%s&orgID=%s&passwordset=%t&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, "", testCode, preferredLoginName, orgID, false, userID)
w.message = messages.Email{ w.message = messages.Email{
Recipients: []string{lastEmail}, Recipients: []string{lastEmail},
Subject: expectMailSubject, Subject: expectMailSubject,
@ -196,6 +197,46 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) {
}, },
}, w }, w
}, },
}, {
name: "button url without event trigger url with authRequestID",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}"
testCode := "testcode"
expectContent := fmt.Sprintf("%s://%s:%d/ui/login/user/init?authRequestID=%s&code=%s&loginname=%s&orgID=%s&passwordset=%t&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, authRequestID, testCode, preferredLoginName, orgID, false, userID)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
Content: expectContent,
}
codeAlg, code := cryptoValue(t, ctrl, testCode)
queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{
Domains: []*query.InstanceDomain{{
Domain: instancePrimaryDomain,
IsPrimary: true,
}},
}, nil)
expectTemplateQueries(queries, givenTemplate)
commands.EXPECT().HumanInitCodeSent(gomock.Any(), orgID, userID).Return(nil)
return fields{
queries: queries,
commands: commands,
es: eventstore.NewEventstore(&eventstore.Config{
Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier,
}),
userDataCrypto: codeAlg,
}, args{
event: &user.HumanInitialCodeAddedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{
AggregateID: userID,
ResourceOwner: sql.NullString{String: orgID},
CreationDate: time.Now().UTC(),
}),
Code: code,
Expiry: time.Hour,
AuthRequestID: authRequestID,
},
}, w
},
}} }}
// TODO: Why don't we have an url template on user.HumanInitialCodeAddedEvent? // TODO: Why don't we have an url template on user.HumanInitialCodeAddedEvent?
for _, tt := range tests { for _, tt := range tests {
@ -305,7 +346,7 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) {
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}" givenTemplate := "{{.URL}}"
testCode := "testcode" testCode := "testcode"
expectContent := fmt.Sprintf("%s/ui/login/mail/verification?userID=%s&code=%s&orgID=%s", eventOrigin, userID, testCode, orgID) expectContent := fmt.Sprintf("%s/ui/login/mail/verification?authRequestID=%s&code=%s&orgID=%s&userID=%s", eventOrigin, "", testCode, orgID, userID)
w.message = messages.Email{ w.message = messages.Email{
Recipients: []string{lastEmail}, Recipients: []string{lastEmail},
Subject: expectMailSubject, Subject: expectMailSubject,
@ -342,7 +383,7 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) {
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}" givenTemplate := "{{.URL}}"
testCode := "testcode" testCode := "testcode"
expectContent := fmt.Sprintf("%s://%s:%d/ui/login/mail/verification?userID=%s&code=%s&orgID=%s", externalProtocol, instancePrimaryDomain, externalPort, userID, testCode, orgID) expectContent := fmt.Sprintf("%s://%s:%d/ui/login/mail/verification?authRequestID=%s&code=%s&orgID=%s&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, "", testCode, orgID, userID)
w.message = messages.Email{ w.message = messages.Email{
Recipients: []string{lastEmail}, Recipients: []string{lastEmail},
Subject: expectMailSubject, Subject: expectMailSubject,
@ -378,6 +419,48 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) {
}, },
}, w }, w
}, },
}, {
name: "button url without event trigger url with authRequestID",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}"
testCode := "testcode"
expectContent := fmt.Sprintf("%s://%s:%d/ui/login/mail/verification?authRequestID=%s&code=%s&orgID=%s&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, authRequestID, testCode, orgID, userID)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
Content: expectContent,
}
codeAlg, code := cryptoValue(t, ctrl, testCode)
queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{
Domains: []*query.InstanceDomain{{
Domain: instancePrimaryDomain,
IsPrimary: true,
}},
}, nil)
expectTemplateQueries(queries, givenTemplate)
commands.EXPECT().HumanEmailVerificationCodeSent(gomock.Any(), orgID, userID).Return(nil)
return fields{
queries: queries,
commands: commands,
es: eventstore.NewEventstore(&eventstore.Config{
Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier,
}),
userDataCrypto: codeAlg,
}, args{
event: &user.HumanEmailCodeAddedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{
AggregateID: userID,
ResourceOwner: sql.NullString{String: orgID},
CreationDate: time.Now().UTC(),
}),
Code: code,
Expiry: time.Hour,
URLTemplate: "",
CodeReturned: false,
AuthRequestID: authRequestID,
},
}, w
},
}, { }, {
name: "button url with url template and event trigger url", name: "button url with url template and event trigger url",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
@ -524,7 +607,7 @@ func Test_userNotifier_reducePasswordCodeAdded(t *testing.T) {
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}" givenTemplate := "{{.URL}}"
testCode := "testcode" testCode := "testcode"
expectContent := fmt.Sprintf("%s/ui/login/password/init?userID=%s&code=%s&orgID=%s", eventOrigin, userID, testCode, orgID) expectContent := fmt.Sprintf("%s/ui/login/password/init?authRequestID=%s&code=%s&orgID=%s&userID=%s", eventOrigin, "", testCode, orgID, userID)
w.message = messages.Email{ w.message = messages.Email{
Recipients: []string{lastEmail}, Recipients: []string{lastEmail},
Subject: expectMailSubject, Subject: expectMailSubject,
@ -561,7 +644,7 @@ func Test_userNotifier_reducePasswordCodeAdded(t *testing.T) {
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}" givenTemplate := "{{.URL}}"
testCode := "testcode" testCode := "testcode"
expectContent := fmt.Sprintf("%s://%s:%d/ui/login/password/init?userID=%s&code=%s&orgID=%s", externalProtocol, instancePrimaryDomain, externalPort, userID, testCode, orgID) expectContent := fmt.Sprintf("%s://%s:%d/ui/login/password/init?authRequestID=%s&code=%s&orgID=%s&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, "", testCode, orgID, userID)
w.message = messages.Email{ w.message = messages.Email{
Recipients: []string{lastEmail}, Recipients: []string{lastEmail},
Subject: expectMailSubject, Subject: expectMailSubject,
@ -597,6 +680,48 @@ func Test_userNotifier_reducePasswordCodeAdded(t *testing.T) {
}, },
}, w }, w
}, },
}, {
name: "button url without event trigger url with authRequestID",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}"
testCode := "testcode"
expectContent := fmt.Sprintf("%s://%s:%d/ui/login/password/init?authRequestID=%s&code=%s&orgID=%s&userID=%s", externalProtocol, instancePrimaryDomain, externalPort, authRequestID, testCode, orgID, userID)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
Content: expectContent,
}
codeAlg, code := cryptoValue(t, ctrl, testCode)
queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{
Domains: []*query.InstanceDomain{{
Domain: instancePrimaryDomain,
IsPrimary: true,
}},
}, nil)
expectTemplateQueries(queries, givenTemplate)
commands.EXPECT().PasswordCodeSent(gomock.Any(), orgID, userID).Return(nil)
return fields{
queries: queries,
commands: commands,
es: eventstore.NewEventstore(&eventstore.Config{
Querier: es_repo_mock.NewRepo(t).ExpectFilterEvents().MockQuerier,
}),
userDataCrypto: codeAlg,
}, args{
event: &user.HumanPasswordCodeAddedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{
AggregateID: userID,
ResourceOwner: sql.NullString{String: orgID},
CreationDate: time.Now().UTC(),
}),
Code: code,
Expiry: time.Hour,
URLTemplate: "",
CodeReturned: false,
AuthRequestID: authRequestID,
},
}, w
},
}, { }, {
name: "button url with url template and event trigger url", name: "button url with url template and event trigger url",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) { test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {

View File

@ -10,10 +10,10 @@ import (
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
) )
func (notify Notify) SendEmailVerificationCode(ctx context.Context, user *query.NotifyUser, code string, urlTmpl string) error { func (notify Notify) SendEmailVerificationCode(ctx context.Context, user *query.NotifyUser, code string, urlTmpl, authRequestID string) error {
var url string var url string
if urlTmpl == "" { if urlTmpl == "" {
url = login.MailVerificationLink(http_utils.ComposedOrigin(ctx), user.ID, code, user.ResourceOwner) url = login.MailVerificationLink(http_utils.ComposedOrigin(ctx), user.ID, code, user.ResourceOwner, authRequestID)
} else { } else {
var buf strings.Builder var buf strings.Builder
if err := domain.RenderConfirmURLTemplate(&buf, urlTmpl, user.ID, code, user.ResourceOwner); err != nil { if err := domain.RenderConfirmURLTemplate(&buf, urlTmpl, user.ID, code, user.ResourceOwner); err != nil {

View File

@ -15,10 +15,11 @@ import (
func TestNotify_SendEmailVerificationCode(t *testing.T) { func TestNotify_SendEmailVerificationCode(t *testing.T) {
type args struct { type args struct {
user *query.NotifyUser user *query.NotifyUser
origin string origin string
code string code string
urlTmpl string urlTmpl string
authRequestID string
} }
tests := []struct { tests := []struct {
name string name string
@ -33,12 +34,13 @@ func TestNotify_SendEmailVerificationCode(t *testing.T) {
ID: "user1", ID: "user1",
ResourceOwner: "org1", ResourceOwner: "org1",
}, },
origin: "https://example.com", origin: "https://example.com",
code: "123", code: "123",
urlTmpl: "", urlTmpl: "",
authRequestID: "authRequestID",
}, },
want: &notifyResult{ want: &notifyResult{
url: "https://example.com/ui/login/mail/verification?userID=user1&code=123&orgID=org1", url: "https://example.com/ui/login/mail/verification?authRequestID=authRequestID&code=123&orgID=org1&userID=user1",
args: map[string]interface{}{"Code": "123"}, args: map[string]interface{}{"Code": "123"},
messageType: domain.VerifyEmailMessageType, messageType: domain.VerifyEmailMessageType,
allowUnverifiedNotificationChannel: true, allowUnverifiedNotificationChannel: true,
@ -51,9 +53,10 @@ func TestNotify_SendEmailVerificationCode(t *testing.T) {
ID: "user1", ID: "user1",
ResourceOwner: "org1", ResourceOwner: "org1",
}, },
origin: "https://example.com", origin: "https://example.com",
code: "123", code: "123",
urlTmpl: "{{", urlTmpl: "{{",
authRequestID: "authRequestID",
}, },
want: &notifyResult{}, want: &notifyResult{},
wantErr: zerrors.ThrowInvalidArgument(nil, "DOMAIN-oGh5e", "Errors.User.InvalidURLTemplate"), wantErr: zerrors.ThrowInvalidArgument(nil, "DOMAIN-oGh5e", "Errors.User.InvalidURLTemplate"),
@ -65,9 +68,10 @@ func TestNotify_SendEmailVerificationCode(t *testing.T) {
ID: "user1", ID: "user1",
ResourceOwner: "org1", ResourceOwner: "org1",
}, },
origin: "https://example.com", origin: "https://example.com",
code: "123", code: "123",
urlTmpl: "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}", urlTmpl: "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}",
authRequestID: "authRequestID",
}, },
want: &notifyResult{ want: &notifyResult{
url: "https://example.com/email/verify?userID=user1&code=123&orgID=org1", url: "https://example.com/email/verify?userID=user1&code=123&orgID=org1",
@ -80,7 +84,7 @@ func TestNotify_SendEmailVerificationCode(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, notify := mockNotify() got, notify := mockNotify()
err := notify.SendEmailVerificationCode(http_utils.WithComposedOrigin(context.Background(), tt.args.origin), tt.args.user, tt.args.code, tt.args.urlTmpl) err := notify.SendEmailVerificationCode(http_utils.WithComposedOrigin(context.Background(), tt.args.origin), tt.args.user, tt.args.code, tt.args.urlTmpl, tt.args.authRequestID)
require.ErrorIs(t, err, tt.wantErr) require.ErrorIs(t, err, tt.wantErr)
assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, got)
}) })

View File

@ -9,8 +9,8 @@ import (
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
) )
func (notify Notify) SendUserInitCode(ctx context.Context, user *query.NotifyUser, code string) error { func (notify Notify) SendUserInitCode(ctx context.Context, user *query.NotifyUser, code, authRequestID string) error {
url := login.InitUserLink(http_utils.ComposedOrigin(ctx), user.ID, user.PreferredLoginName, code, user.ResourceOwner, user.PasswordSet) url := login.InitUserLink(http_utils.ComposedOrigin(ctx), user.ID, user.PreferredLoginName, code, user.ResourceOwner, user.PasswordSet, authRequestID)
args := make(map[string]interface{}) args := make(map[string]interface{})
args["Code"] = code args["Code"] = code
return notify(url, args, domain.InitCodeMessageType, true) return notify(url, args, domain.InitCodeMessageType, true)

View File

@ -10,10 +10,10 @@ import (
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
) )
func (notify Notify) SendPasswordCode(ctx context.Context, user *query.NotifyUser, code, urlTmpl string) error { func (notify Notify) SendPasswordCode(ctx context.Context, user *query.NotifyUser, code, urlTmpl, authRequestID string) error {
var url string var url string
if urlTmpl == "" { if urlTmpl == "" {
url = login.InitPasswordLink(http_utils.ComposedOrigin(ctx), user.ID, code, user.ResourceOwner) url = login.InitPasswordLink(http_utils.ComposedOrigin(ctx), user.ID, code, user.ResourceOwner, authRequestID)
} else { } else {
var buf strings.Builder var buf strings.Builder
if err := domain.RenderConfirmURLTemplate(&buf, urlTmpl, user.ID, code, user.ResourceOwner); err != nil { if err := domain.RenderConfirmURLTemplate(&buf, urlTmpl, user.ID, code, user.ResourceOwner); err != nil {

View File

@ -157,6 +157,8 @@ type HumanRegisteredEvent struct {
Secret *crypto.CryptoValue `json:"secret,omitempty"` // legacy Secret *crypto.CryptoValue `json:"secret,omitempty"` // legacy
EncodedHash string `json:"encodedHash,omitempty"` EncodedHash string `json:"encodedHash,omitempty"`
ChangeRequired bool `json:"changeRequired,omitempty"` ChangeRequired bool `json:"changeRequired,omitempty"`
UserAgentID string `json:"userAgentID,omitempty"`
} }
func (e *HumanRegisteredEvent) Payload() interface{} { func (e *HumanRegisteredEvent) Payload() interface{} {
@ -208,6 +210,7 @@ func NewHumanRegisteredEvent(
gender domain.Gender, gender domain.Gender,
emailAddress domain.EmailAddress, emailAddress domain.EmailAddress,
userLoginMustBeDomain bool, userLoginMustBeDomain bool,
userAgentID string,
) *HumanRegisteredEvent { ) *HumanRegisteredEvent {
return &HumanRegisteredEvent{ return &HumanRegisteredEvent{
BaseEvent: *eventstore.NewBaseEventForPush( BaseEvent: *eventstore.NewBaseEventForPush(
@ -224,6 +227,7 @@ func NewHumanRegisteredEvent(
Gender: gender, Gender: gender,
EmailAddress: emailAddress, EmailAddress: emailAddress,
userLoginMustBeDomain: userLoginMustBeDomain, userLoginMustBeDomain: userLoginMustBeDomain,
UserAgentID: userAgentID,
} }
} }
@ -244,6 +248,7 @@ type HumanInitialCodeAddedEvent struct {
Code *crypto.CryptoValue `json:"code,omitempty"` Code *crypto.CryptoValue `json:"code,omitempty"`
Expiry time.Duration `json:"expiry,omitempty"` Expiry time.Duration `json:"expiry,omitempty"`
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"` TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
AuthRequestID string `json:"authRequestID,omitempty"`
} }
func (e *HumanInitialCodeAddedEvent) Payload() interface{} { func (e *HumanInitialCodeAddedEvent) Payload() interface{} {
@ -263,6 +268,7 @@ func NewHumanInitialCodeAddedEvent(
aggregate *eventstore.Aggregate, aggregate *eventstore.Aggregate,
code *crypto.CryptoValue, code *crypto.CryptoValue,
expiry time.Duration, expiry time.Duration,
authRequestID string,
) *HumanInitialCodeAddedEvent { ) *HumanInitialCodeAddedEvent {
return &HumanInitialCodeAddedEvent{ return &HumanInitialCodeAddedEvent{
BaseEvent: *eventstore.NewBaseEventForPush( BaseEvent: *eventstore.NewBaseEventForPush(
@ -273,6 +279,7 @@ func NewHumanInitialCodeAddedEvent(
Code: code, Code: code,
Expiry: expiry, Expiry: expiry,
TriggeredAtOrigin: http.ComposedOrigin(ctx), TriggeredAtOrigin: http.ComposedOrigin(ctx),
AuthRequestID: authRequestID,
} }
} }

View File

@ -126,6 +126,8 @@ type HumanEmailCodeAddedEvent struct {
URLTemplate string `json:"url_template,omitempty"` URLTemplate string `json:"url_template,omitempty"`
CodeReturned bool `json:"code_returned,omitempty"` CodeReturned bool `json:"code_returned,omitempty"`
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"` TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
// AuthRequest is only used in V1 Login UI
AuthRequestID string `json:"authRequestID,omitempty"`
} }
func (e *HumanEmailCodeAddedEvent) Payload() interface{} { func (e *HumanEmailCodeAddedEvent) Payload() interface{} {
@ -145,8 +147,19 @@ func NewHumanEmailCodeAddedEvent(
aggregate *eventstore.Aggregate, aggregate *eventstore.Aggregate,
code *crypto.CryptoValue, code *crypto.CryptoValue,
expiry time.Duration, expiry time.Duration,
authRequestID string,
) *HumanEmailCodeAddedEvent { ) *HumanEmailCodeAddedEvent {
return NewHumanEmailCodeAddedEventV2(ctx, aggregate, code, expiry, "", false) return &HumanEmailCodeAddedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
HumanEmailCodeAddedType,
),
Code: code,
Expiry: expiry,
TriggeredAtOrigin: http.ComposedOrigin(ctx),
AuthRequestID: authRequestID,
}
} }
func NewHumanEmailCodeAddedEventV2( func NewHumanEmailCodeAddedEventV2(

View File

@ -87,6 +87,8 @@ type HumanPasswordCodeAddedEvent struct {
URLTemplate string `json:"url_template,omitempty"` URLTemplate string `json:"url_template,omitempty"`
CodeReturned bool `json:"code_returned,omitempty"` CodeReturned bool `json:"code_returned,omitempty"`
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"` TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
// AuthRequest is only used in V1 Login UI
AuthRequestID string `json:"authRequestID,omitempty"`
} }
func (e *HumanPasswordCodeAddedEvent) Payload() interface{} { func (e *HumanPasswordCodeAddedEvent) Payload() interface{} {
@ -107,8 +109,20 @@ func NewHumanPasswordCodeAddedEvent(
code *crypto.CryptoValue, code *crypto.CryptoValue,
expiry time.Duration, expiry time.Duration,
notificationType domain.NotificationType, notificationType domain.NotificationType,
authRequestID string,
) *HumanPasswordCodeAddedEvent { ) *HumanPasswordCodeAddedEvent {
return NewHumanPasswordCodeAddedEventV2(ctx, aggregate, code, expiry, notificationType, "", false) return &HumanPasswordCodeAddedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
HumanPasswordCodeAddedType,
),
Code: code,
Expiry: expiry,
NotificationType: notificationType,
TriggeredAtOrigin: http.ComposedOrigin(ctx),
AuthRequestID: authRequestID,
}
} }
func NewHumanPasswordCodeAddedEventV2( func NewHumanPasswordCodeAddedEventV2(

View File

@ -111,6 +111,7 @@ Errors:
Key: Key:
NotFound: Машинният ключ не е намерен NotFound: Машинният ключ не е намерен
AlreadyExisting: Машинният ключ вече съществува AlreadyExisting: Машинният ключ вече съществува
Invalid: Публичният ключ не е валиден RSA публичен ключ във формат PKIX с PEM кодиране
Secret: Secret:
NotExisting: Тайната не съществува NotExisting: Тайната не съществува
Invalid: Тайната е невалидна Invalid: Тайната е невалидна

View File

@ -109,6 +109,7 @@ Errors:
Key: Key:
NotFound: Klíč stroje nenalezen NotFound: Klíč stroje nenalezen
AlreadyExisting: Klíč stroje již existuje AlreadyExisting: Klíč stroje již existuje
Invalid: Veřejný klíč není platný veřejný klíč RSA ve formátu PKIX s kódováním PEM
Secret: Secret:
NotExisting: Tajemství neexistuje NotExisting: Tajemství neexistuje
Invalid: Tajemství je neplatné Invalid: Tajemství je neplatné

View File

@ -109,6 +109,7 @@ Errors:
Key: Key:
NotFound: Maschinen Schlüssel nicht gefunden NotFound: Maschinen Schlüssel nicht gefunden
AlreadyExisting: Machine Schlüssel exisiert bereits AlreadyExisting: Machine Schlüssel exisiert bereits
Invalid: Der öffentliche Schlüssel ist kein gültiger öffentlicher RSA-Schlüssel im PKIX-Format mit PEM-Kodierung
Secret: Secret:
NotExisting: Secret existiert nicht NotExisting: Secret existiert nicht
Invalid: Secret ist ungültig Invalid: Secret ist ungültig

View File

@ -109,6 +109,7 @@ Errors:
Key: Key:
NotFound: Machine key not found NotFound: Machine key not found
AlreadyExisting: Machine key already existing AlreadyExisting: Machine key already existing
Invalid: Public key is not a valid RSA public key in PKIX format with PEM encoding
Secret: Secret:
NotExisting: Secret doesn't exist NotExisting: Secret doesn't exist
Invalid: Secret is invalid Invalid: Secret is invalid

View File

@ -109,6 +109,7 @@ Errors:
Key: Key:
NotFound: Clave de máquina no encontrada NotFound: Clave de máquina no encontrada
AlreadyExisting: La clave de máquina ya existe AlreadyExisting: La clave de máquina ya existe
Invalid: La clave pública no es una clave pública RSA válida en formato PKIX con codificación PEM
Secret: Secret:
NotExisting: El secreto no existe NotExisting: El secreto no existe
Invalid: El secret no es válido Invalid: El secret no es válido

View File

@ -109,6 +109,7 @@ Errors:
Key: Key:
NotFound: Clé de la machine non trouvée NotFound: Clé de la machine non trouvée
AlreadyExisting: Clé de la machine déjà existante AlreadyExisting: Clé de la machine déjà existante
Invalid: La clé publique n'est pas une clé publique RSA valide au format PKIX avec encodage PEM
Secret: Secret:
NotExisting: Secret n'existe pas NotExisting: Secret n'existe pas
Invalid: Secret n'est pas valide Invalid: Secret n'est pas valide

View File

@ -109,6 +109,7 @@ Errors:
Key: Key:
NotFound: Chiave macchina non trovato NotFound: Chiave macchina non trovato
AlreadyExisting: Chiave macchina già esistente AlreadyExisting: Chiave macchina già esistente
Invalid: La chiave pubblica non è una chiave pubblica RSA valida in formato PKIX con codifica PEM
Secret: Secret:
NotExisting: Secret non esiste NotExisting: Secret non esiste
Invalid: Secret non è valido Invalid: Secret non è valido

View File

@ -102,6 +102,7 @@ Errors:
Key: Key:
NotFound: マシーンキーが見つかりません NotFound: マシーンキーが見つかりません
AlreadyExisting: すでに存在しているマシーンキーです AlreadyExisting: すでに存在しているマシーンキーです
Invalid: 公開キーは、PEM エンコードを使用した PKIX 形式の有効な RSA 公開キーではありません
Secret: Secret:
NotExisting: シークレットは存在しません NotExisting: シークレットは存在しません
Invalid: 無効なシークレットです Invalid: 無効なシークレットです

View File

@ -109,6 +109,7 @@ Errors:
Key: Key:
NotFound: Machine key не е пронајден NotFound: Machine key не е пронајден
AlreadyExisting: Machine key веќе постои AlreadyExisting: Machine key веќе постои
Invalid: Јавниот клуч не е валиден јавен клуч RSA во формат PKIX со PEM кодирање
Secret: Secret:
NotExisting: Тајната не постои NotExisting: Тајната не постои
Invalid: Тајната е невалидна Invalid: Тајната е невалидна

View File

@ -108,6 +108,7 @@ Errors:
Key: Key:
NotFound: Machine sleutel niet gevonden NotFound: Machine sleutel niet gevonden
AlreadyExisting: Machine sleutel al bestaand AlreadyExisting: Machine sleutel al bestaand
Invalid: De openbare sleutel is geen geldige openbare RSA-sleutel in PKIX-indeling met PEM-codering
Secret: Secret:
NotExisting: Geheim bestaat niet NotExisting: Geheim bestaat niet
Invalid: Geheim is ongeldig Invalid: Geheim is ongeldig

View File

@ -109,6 +109,7 @@ Errors:
Key: Key:
NotFound: Klucz maszyny nie znaleziony NotFound: Klucz maszyny nie znaleziony
AlreadyExisting: Klucz maszyny już istnieje AlreadyExisting: Klucz maszyny już istnieje
Invalid: Klucz publiczny nie jest prawidłowym kluczem publicznym RSA w formacie PKIX z kodowaniem PEM
Secret: Secret:
NotExisting: Sekret nie istnieje NotExisting: Sekret nie istnieje
Invalid: Sekret jest nieprawidłowy Invalid: Sekret jest nieprawidłowy

View File

@ -109,6 +109,7 @@ Errors:
Key: Key:
NotFound: Chave de máquina não encontrada NotFound: Chave de máquina não encontrada
AlreadyExisting: Chave de máquina já existe AlreadyExisting: Chave de máquina já existe
Invalid: A chave pública não é uma chave pública RSA válida no formato PKIX com codificação PEM
Secret: Secret:
NotExisting: Segredo não existe NotExisting: Segredo não existe
Invalid: Segredo é inválido Invalid: Segredo é inválido

View File

@ -110,6 +110,7 @@ Errors:
Key: Key:
NotFound: Машинный ключ не найден NotFound: Машинный ключ не найден
AlreadyExisting: Машинный ключ уже существует AlreadyExisting: Машинный ключ уже существует
Invalid: Открытый ключ не является допустимым открытым ключом RSA в формате PKIX с кодировкой PEM
Secret: Secret:
NotExisting: Ключ не существует NotExisting: Ключ не существует
Invalid: Ключ недействителен Invalid: Ключ недействителен

View File

@ -109,6 +109,7 @@ Errors:
Key: Key:
NotFound: 未找到机器密钥 NotFound: 未找到机器密钥
AlreadyExisting: 已有的机器钥匙 AlreadyExisting: 已有的机器钥匙
Invalid: 公钥不是采用 PEM 编码的 PKIX 格式的有效 RSA 公钥
Secret: Secret:
NotExisting: 秘密并不存在 NotExisting: 秘密并不存在
Invalid: 秘密是无效的 Invalid: 秘密是无效的

View File

@ -0,0 +1,33 @@
package database
type Condition interface {
Write(stmt *Statement, columnName string)
}
type Filter[C compare, V value] struct {
comp C
value V
}
func (f Filter[C, V]) Write(stmt *Statement, columnName string) {
prepareWrite(stmt, columnName, f.comp)
stmt.WriteArg(f.value)
}
func prepareWrite[C compare](stmt *Statement, columnName string, comp C) {
stmt.WriteString(columnName)
stmt.WriteRune(' ')
stmt.WriteString(comp.String())
stmt.WriteRune(' ')
}
type compare interface {
numberCompare | textCompare | listCompare
String() string
}
type value interface {
number | text
// TODO: condition must know if it's args are named parameters or not
// number | text | placeholder
}

View File

@ -0,0 +1,57 @@
package database
import "github.com/zitadel/logging"
type ListFilter[V value] struct {
comp listCompare
list []V
}
func NewListEquals[V value](list ...V) *ListFilter[V] {
return newListFilter[V](listEqual, list)
}
func NewListContains[V value](list ...V) *ListFilter[V] {
return newListFilter[V](listContain, list)
}
func NewListNotContains[V value](list ...V) *ListFilter[V] {
return newListFilter[V](listNotContain, list)
}
func newListFilter[V value](comp listCompare, list []V) *ListFilter[V] {
return &ListFilter[V]{
comp: comp,
list: list,
}
}
func (f ListFilter[V]) Write(stmt *Statement, columnName string) {
if len(f.list) == 0 {
logging.WithFields("column", columnName).Debug("skip list filter because no entries defined")
return
}
if f.comp == listNotContain {
stmt.WriteString("NOT(")
}
stmt.WriteString(columnName)
stmt.WriteString(" = ")
if f.comp != listEqual {
stmt.WriteString("ANY(")
}
stmt.WriteArg(f.list)
if f.comp != listEqual {
stmt.WriteString(")")
}
if f.comp == listNotContain {
stmt.WriteRune(')')
}
}
type listCompare uint8
const (
listEqual listCompare = iota
listContain
listNotContain
)

View File

@ -0,0 +1,122 @@
package database
import (
"reflect"
"testing"
)
func TestNewListConstructors(t *testing.T) {
type args struct {
constructor func(t ...string) *ListFilter[string]
t []string
}
tests := []struct {
name string
args args
want *ListFilter[string]
}{
{
name: "NewListEquals",
args: args{
constructor: NewListEquals[string],
t: []string{"as", "df"},
},
want: &ListFilter[string]{
comp: listEqual,
list: []string{"as", "df"},
},
},
{
name: "NewListContains",
args: args{
constructor: NewListContains[string],
t: []string{"as", "df"},
},
want: &ListFilter[string]{
comp: listContain,
list: []string{"as", "df"},
},
},
{
name: "NewListNotContains",
args: args{
constructor: NewListNotContains[string],
t: []string{"as", "df"},
},
want: &ListFilter[string]{
comp: listNotContain,
list: []string{"as", "df"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.args.constructor(tt.args.t...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("number constructor = %v, want %v", got, tt.want)
}
})
}
}
func TestNewListConditionWrite(t *testing.T) {
type args struct {
constructor func(t ...string) *ListFilter[string]
t []string
}
tests := []struct {
name string
args args
want wantQuery
}{
{
name: "ListEquals",
args: args{
constructor: NewListEquals[string],
t: []string{"as", "df"},
},
want: wantQuery{
query: "test = $1",
args: []any{[]string{"as", "df"}},
},
},
{
name: "ListContains",
args: args{
constructor: NewListContains[string],
t: []string{"as", "df"},
},
want: wantQuery{
query: "test = ANY($1)",
args: []any{[]string{"as", "df"}},
},
},
{
name: "ListNotContains",
args: args{
constructor: NewListNotContains[string],
t: []string{"as", "df"},
},
want: wantQuery{
query: "NOT(test = ANY($1))",
args: []any{[]string{"as", "df"}},
},
},
{
name: "empty list",
args: args{
constructor: NewListNotContains[string],
},
want: wantQuery{
query: "",
args: nil,
},
},
}
for _, tt := range tests {
var stmt Statement
t.Run(tt.name, func(t *testing.T) {
tt.args.constructor(tt.args.t...).Write(&stmt, "test")
assertQuery(t, &stmt, tt.want)
})
}
}

View File

@ -0,0 +1,139 @@
package mock
import (
"database/sql"
"database/sql/driver"
"reflect"
"testing"
"github.com/DATA-DOG/go-sqlmock"
)
type SQLMock struct {
DB *sql.DB
mock sqlmock.Sqlmock
}
type Expectation func(m sqlmock.Sqlmock)
func NewSQLMock(t *testing.T, expectations ...Expectation) *SQLMock {
db, mock, err := sqlmock.New(
sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual),
sqlmock.ValueConverterOption(new(TypeConverter)),
)
if err != nil {
t.Fatal("create mock failed", err)
}
for _, expectation := range expectations {
expectation(mock)
}
return &SQLMock{
DB: db,
mock: mock,
}
}
func (m *SQLMock) Assert(t *testing.T) {
t.Helper()
if err := m.mock.ExpectationsWereMet(); err != nil {
t.Errorf("expectations not met: %v", err)
}
m.DB.Close()
}
func ExpectBegin(err error) Expectation {
return func(m sqlmock.Sqlmock) {
e := m.ExpectBegin()
if err != nil {
e.WillReturnError(err)
}
}
}
func ExpectCommit(err error) Expectation {
return func(m sqlmock.Sqlmock) {
e := m.ExpectCommit()
if err != nil {
e.WillReturnError(err)
}
}
}
type ExecOpt func(e *sqlmock.ExpectedExec) *sqlmock.ExpectedExec
func WithExecArgs(args ...driver.Value) ExecOpt {
return func(e *sqlmock.ExpectedExec) *sqlmock.ExpectedExec {
return e.WithArgs(args...)
}
}
func WithExecErr(err error) ExecOpt {
return func(e *sqlmock.ExpectedExec) *sqlmock.ExpectedExec {
return e.WillReturnError(err)
}
}
func WithExecNoRowsAffected() ExecOpt {
return func(e *sqlmock.ExpectedExec) *sqlmock.ExpectedExec {
return e.WillReturnResult(driver.ResultNoRows)
}
}
func WithExecRowsAffected(affected driver.RowsAffected) ExecOpt {
return func(e *sqlmock.ExpectedExec) *sqlmock.ExpectedExec {
return e.WillReturnResult(affected)
}
}
func ExpectExec(stmt string, opts ...ExecOpt) Expectation {
return func(m sqlmock.Sqlmock) {
e := m.ExpectExec(stmt)
for _, opt := range opts {
e = opt(e)
}
}
}
type QueryOpt func(m sqlmock.Sqlmock, e *sqlmock.ExpectedQuery) *sqlmock.ExpectedQuery
func WithQueryArgs(args ...driver.Value) QueryOpt {
return func(_ sqlmock.Sqlmock, e *sqlmock.ExpectedQuery) *sqlmock.ExpectedQuery {
return e.WithArgs(args...)
}
}
func WithQueryErr(err error) QueryOpt {
return func(_ sqlmock.Sqlmock, e *sqlmock.ExpectedQuery) *sqlmock.ExpectedQuery {
return e.WillReturnError(err)
}
}
func WithQueryResult(columns []string, rows [][]driver.Value) QueryOpt {
return func(m sqlmock.Sqlmock, e *sqlmock.ExpectedQuery) *sqlmock.ExpectedQuery {
mockedRows := m.NewRows(columns)
for _, row := range rows {
mockedRows = mockedRows.AddRow(row...)
}
return e.WillReturnRows(mockedRows)
}
}
func ExpectQuery(stmt string, opts ...QueryOpt) Expectation {
return func(m sqlmock.Sqlmock) {
e := m.ExpectQuery(stmt)
for _, opt := range opts {
e = opt(m, e)
}
}
}
type AnyType[T interface{}] struct{}
// Match satisfies sqlmock.Argument interface
func (a AnyType[T]) Match(v driver.Value) bool {
return reflect.TypeOf(new(T)).Elem().Kind().String() == reflect.TypeOf(v).Kind().String()
}

View File

@ -0,0 +1,78 @@
package mock
import (
"database/sql/driver"
"encoding/hex"
"encoding/json"
"reflect"
"strconv"
"strings"
)
var _ driver.ValueConverter = (*TypeConverter)(nil)
type TypeConverter struct{}
// ConvertValue converts a value to a driver Value.
func (s TypeConverter) ConvertValue(v any) (driver.Value, error) {
if driver.IsValue(v) {
return v, nil
}
value := reflect.ValueOf(v)
if rawMessage, ok := v.(json.RawMessage); ok {
return convertBytes(rawMessage), nil
}
if value.Kind() == reflect.Slice {
//nolint: exhaustive
// only defined types
switch value.Type().Elem().Kind() {
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return convertSigned(value), nil
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return convertUnsigned(value), nil
case reflect.String:
return convertText(value), nil
}
}
return v, nil
}
// converts a text array to valid pgx v5 representation
func convertSigned(array reflect.Value) string {
slice := make([]string, array.Len())
for i := 0; i < array.Len(); i++ {
slice[i] = strconv.FormatInt(array.Index(i).Int(), 10)
}
return "{" + strings.Join(slice, ",") + "}"
}
// converts a text array to valid pgx v5 representation
func convertUnsigned(array reflect.Value) string {
slice := make([]string, array.Len())
for i := 0; i < array.Len(); i++ {
slice[i] = strconv.FormatUint(array.Index(i).Uint(), 10)
}
return "{" + strings.Join(slice, ",") + "}"
}
// converts a text array to valid pgx v5 representation
func convertText(array reflect.Value) string {
slice := make([]string, array.Len())
for i := 0; i < array.Len(); i++ {
slice[i] = array.Index(i).String()
}
return "{" + strings.Join(slice, ",") + "}"
}
func convertBytes(array []byte) string {
var builder strings.Builder
builder.Grow(hex.EncodedLen(len(array)) + 4)
builder.WriteString(`\x`)
builder.Write(hex.AppendEncode(nil, array))
return builder.String()
}

View File

@ -0,0 +1,100 @@
package database
import (
"time"
"github.com/zitadel/logging"
"golang.org/x/exp/constraints"
)
type NumberFilter[N number] struct {
Filter[numberCompare, N]
}
func NewNumberEquals[N number](n N) *NumberFilter[N] {
return newNumberFilter(numberEqual, n)
}
func NewNumberAtLeast[N number](n N) *NumberFilter[N] {
return newNumberFilter(numberAtLeast, n)
}
func NewNumberAtMost[N number](n N) *NumberFilter[N] {
return newNumberFilter(numberAtMost, n)
}
func NewNumberGreater[N number](n N) *NumberFilter[N] {
return newNumberFilter(numberGreater, n)
}
func NewNumberLess[N number](n N) *NumberFilter[N] {
return newNumberFilter(numberLess, n)
}
func NewNumberUnequal[N number](n N) *NumberFilter[N] {
return newNumberFilter(numberUnequal, n)
}
func newNumberFilter[N number](comp numberCompare, n N) *NumberFilter[N] {
return &NumberFilter[N]{
Filter: Filter[numberCompare, N]{
comp: comp,
value: n,
},
}
}
// NumberBetweenFilter combines [AtLeast] and [AtMost] comparisons
type NumberBetweenFilter[N number] struct {
min, max N
}
func NewNumberBetween[N number](min, max N) *NumberBetweenFilter[N] {
return &NumberBetweenFilter[N]{
min: min,
max: max,
}
}
func (f NumberBetweenFilter[N]) Write(stmt *Statement, columnName string) {
NewNumberAtLeast[N](f.min).Write(stmt, columnName)
stmt.WriteString(" AND ")
NewNumberAtMost[N](f.max).Write(stmt, columnName)
}
type numberCompare uint8
const (
numberEqual numberCompare = iota
numberAtLeast
numberAtMost
numberGreater
numberLess
numberUnequal
)
func (c numberCompare) String() string {
switch c {
case numberEqual:
return "="
case numberAtLeast:
return ">="
case numberAtMost:
return "<="
case numberGreater:
return ">"
case numberLess:
return "<"
case numberUnequal:
return "<>"
default:
logging.WithFields("compare", c).Panic("comparison type not implemented")
return ""
}
}
type number interface {
constraints.Integer | constraints.Float | time.Time
// TODO: condition must know if it's args are named parameters or not
// constraints.Integer | constraints.Float | time.Time | placeholder
}

Some files were not shown because too many files have changed in this diff Show More