mirror of
https://github.com/zitadel/zitadel.git
synced 2025-03-01 15:07:24 +00:00
Merge branch 'main' into next
This commit is contained in:
commit
6e60335789
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -52,6 +52,8 @@ jobs:
|
||||
go_version: "1.22"
|
||||
core_cache_key: ${{ needs.core.outputs.cache_key }}
|
||||
core_cache_path: ${{ needs.core.outputs.cache_path }}
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
lint:
|
||||
needs: [core, console]
|
||||
|
3
.github/workflows/core-test.yml
vendored
3
.github/workflows/core-test.yml
vendored
@ -12,6 +12,9 @@ on:
|
||||
core_cache_path:
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
CODECOV_TOKEN:
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
postgres:
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -82,3 +82,7 @@ go.work
|
||||
go.work.sum
|
||||
# Local Netlify folder
|
||||
.netlify
|
||||
|
||||
load-test/node_modules
|
||||
load-test/yarn-error.log
|
||||
load-test/dist
|
15
README.md
15
README.md
@ -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)
|
||||
- [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
|
||||
- [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
|
||||
@ -107,16 +107,17 @@ Yet it offers everything you need for a customer identity ([CIAM](https://zitade
|
||||
Authentication
|
||||
|
||||
- Single Sign On (SSO)
|
||||
- Passkeys support (FIDO2 / WebAuthN)
|
||||
- [Passkeys support (FIDO2 / WebAuthN)](https://zitadel.com/docs/concepts/features/passkeys)
|
||||
- Username / Password
|
||||
- Multifactor authentication with OTP, U2F, Email OTP, SMS OTP
|
||||
- LDAP
|
||||
- External enterprise identity providers and social logins
|
||||
- [LDAP](https://zitadel.com/docs/guides/integrate/identity-providers/ldap)
|
||||
- [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)
|
||||
- [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)
|
||||
- [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
|
||||
|
||||
@ -130,6 +131,10 @@ Integration
|
||||
- [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
|
||||
- [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-registration](https://zitadel.com/docs/concepts/features/selfservice#registration) including verification
|
||||
|
@ -61,7 +61,7 @@
|
||||
<ng-container matColumnDef="expirationDate">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'USER.MACHINE.EXPIRATIONDATE' | translate }}</th>
|
||||
<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>
|
||||
</ng-container>
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
<span>{{ length }} </span>{{ 'PAGINATOR.COUNT' | translate }}
|
||||
</p>
|
||||
<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>
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
|
@ -56,7 +56,7 @@
|
||||
<ng-container matColumnDef="expirationDate">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'USER.MACHINE.EXPIRATIONDATE' | translate }}</th>
|
||||
<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>
|
||||
</ng-container>
|
||||
|
||||
|
@ -13,14 +13,14 @@
|
||||
<div class="row">
|
||||
<p class="left cnsl-secondary-text">{{ 'USER.MACHINE.CREATIONDATE' | translate }}</p>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="row" *ngIf="expirationDate">
|
||||
<p class="left cnsl-secondary-text">{{ 'USER.MACHINE.EXPIRATIONDATE' | translate }}</p>
|
||||
<p class="right">
|
||||
{{ expirationDate | localizedDate: 'EEE dd. MMM YYYY, HH:mm' }}
|
||||
{{ expirationDate | localizedDate: 'EEE dd. MMM yyyy, HH:mm' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -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 { Org, OrgState } from 'src/app/proto/generated/zitadel/org_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 { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
@ -48,6 +49,7 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
|
||||
private auth: GrpcAuthService,
|
||||
private dialog: MatDialog,
|
||||
public mgmtService: ManagementService,
|
||||
private adminService: AdminService,
|
||||
private toast: ToastService,
|
||||
private router: Router,
|
||||
breadcrumbService: BreadcrumbService,
|
||||
@ -146,15 +148,34 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
|
||||
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) => {
|
||||
if (resp) {
|
||||
this.mgmtService
|
||||
.removeOrg()
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
this.router.navigate(['/orgs']);
|
||||
}, 1000);
|
||||
this.toast.showInfo('ORG.TOAST.DELETED', true);
|
||||
this.adminService
|
||||
.getDefaultOrg()
|
||||
.then((response) => {
|
||||
const org = response?.org;
|
||||
if (org) {
|
||||
// We now remove the org
|
||||
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) => {
|
||||
this.toast.showError(error);
|
||||
|
@ -90,6 +90,8 @@ import {
|
||||
GetDefaultLanguageResponse,
|
||||
GetDefaultLoginTextsRequest,
|
||||
GetDefaultLoginTextsResponse,
|
||||
GetDefaultOrgRequest,
|
||||
GetDefaultOrgResponse,
|
||||
GetDefaultPasswordChangeMessageTextRequest,
|
||||
GetDefaultPasswordChangeMessageTextResponse,
|
||||
GetDefaultPasswordlessRegistrationMessageTextRequest,
|
||||
@ -429,6 +431,11 @@ export class AdminService {
|
||||
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> {
|
||||
const req = new SetDefaultOrgRequest();
|
||||
req.setOrgId(orgId);
|
||||
|
@ -353,6 +353,7 @@ import {
|
||||
RemoveOrgMetadataRequest,
|
||||
RemoveOrgMetadataResponse,
|
||||
RemoveOrgRequest,
|
||||
RemoveOrgResponse,
|
||||
RemovePersonalAccessTokenRequest,
|
||||
RemovePersonalAccessTokenResponse,
|
||||
RemoveProjectGrantMemberRequest,
|
||||
@ -1749,7 +1750,7 @@ export class ManagementService {
|
||||
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();
|
||||
return this.grpcService.mgmt.removeOrg(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
@ -1285,6 +1285,7 @@
|
||||
"MEMBERCHANGED": "Сменен управител.",
|
||||
"SETPRIMARY": "Основен набор от домейни.",
|
||||
"DELETED": "Организацията е изтрита успешно",
|
||||
"DEFAULTORGNOTFOUND": "Организацията по подразбиране не беше намерена",
|
||||
"ORG_WAS_DELETED": "Организацията е изтрита."
|
||||
},
|
||||
"DIALOG": {
|
||||
|
@ -1292,6 +1292,7 @@
|
||||
"MEMBERCHANGED": "Manažer změněn.",
|
||||
"SETPRIMARY": "Nastavena primární doména.",
|
||||
"DELETED": "Organizace úspěšně smazána",
|
||||
"DEFAULTORGNOTFOUND": "Výchozí organizace nebyla nalezena",
|
||||
"ORG_WAS_DELETED": "Organizace byla smazána."
|
||||
},
|
||||
"DIALOG": {
|
||||
|
@ -1291,6 +1291,7 @@
|
||||
"MEMBERCHANGED": "Manager geändert.",
|
||||
"SETPRIMARY": "Primäre Domain gesetzt.",
|
||||
"DELETED": "Organisation erfolgreich gelöscht",
|
||||
"DEFAULTORGNOTFOUND": "Die Standardorganisation wurde nicht gefunden",
|
||||
"ORG_WAS_DELETED": "Organisation wurde gelöscht."
|
||||
},
|
||||
"DIALOG": {
|
||||
|
@ -1291,8 +1291,9 @@
|
||||
"MEMBERREMOVED": "Manager removed.",
|
||||
"MEMBERCHANGED": "Manager changed.",
|
||||
"SETPRIMARY": "Primary domain set.",
|
||||
"DELETED": "Organisation deleted successfully",
|
||||
"ORG_WAS_DELETED": "Organisation has been deleted."
|
||||
"DELETED": "Organization deleted successfully",
|
||||
"DEFAULTORGNOTFOUND": "The default organization was not found",
|
||||
"ORG_WAS_DELETED": "Organization has been deleted."
|
||||
},
|
||||
"DIALOG": {
|
||||
"DEACTIVATE": {
|
||||
|
@ -1293,6 +1293,7 @@
|
||||
"MEMBERCHANGED": "Mánager modificado.",
|
||||
"SETPRIMARY": "Dominio primario establecido.",
|
||||
"DELETED": "Organización borrada con éxito",
|
||||
"DEFAULTORGNOTFOUND": "No se encontró la organización por defecto",
|
||||
"ORG_WAS_DELETED": "La organización ha sido borrada."
|
||||
},
|
||||
"DIALOG": {
|
||||
|
@ -1291,6 +1291,7 @@
|
||||
"MEMBERCHANGED": "Gestionnaire modifié.",
|
||||
"SETPRIMARY": "Domaine primaire défini.",
|
||||
"DELETED": "Organisation supprimée avec succès",
|
||||
"DEFAULTORGNOTFOUND": "L'organisation par défaut est introuvable",
|
||||
"ORG_WAS_DELETED": "L'organisation a été supprimée"
|
||||
},
|
||||
"DIALOG": {
|
||||
|
@ -1291,6 +1291,7 @@
|
||||
"MEMBERCHANGED": "Manager cambiato con successo",
|
||||
"SETPRIMARY": "Dominio primario cambiato con successo",
|
||||
"DELETED": "Organizzazione eliminata con successo",
|
||||
"DEFAULTORGNOTFOUND": "Impossibile trovare l'organizzazione predefinita",
|
||||
"ORG_WAS_DELETED": "Organizzazione è stata eliminata"
|
||||
},
|
||||
"DIALOG": {
|
||||
|
@ -1292,6 +1292,7 @@
|
||||
"MEMBERCHANGED": "マネージャーが変更されました。",
|
||||
"SETPRIMARY": "プライマリドメインが設定されました。",
|
||||
"DELETED": "組織が正常に削除されました。",
|
||||
"DEFAULTORGNOTFOUND": "デフォルトの組織が見つかりませんでした",
|
||||
"ORG_WAS_DELETED": "組織が削除されました。"
|
||||
},
|
||||
"DIALOG": {
|
||||
|
@ -1293,6 +1293,7 @@
|
||||
"MEMBERCHANGED": "Променет менаџер.",
|
||||
"SETPRIMARY": "Поставен основен домен.",
|
||||
"DELETED": "Организацијата успешно избришана",
|
||||
"DEFAULTORGNOTFOUND": "Стандардната организација не беше пронајдена",
|
||||
"ORG_WAS_DELETED": "Организацијата е избришана."
|
||||
},
|
||||
"DIALOG": {
|
||||
|
@ -1292,6 +1292,7 @@
|
||||
"MEMBERCHANGED": "Beheerder gewijzigd.",
|
||||
"SETPRIMARY": "Primaire domein ingesteld.",
|
||||
"DELETED": "Organisatie succesvol verwijderd",
|
||||
"DEFAULTORGNOTFOUND": "De standaardorganisatie is niet gevonden",
|
||||
"ORG_WAS_DELETED": "Organisatie is verwijderd."
|
||||
},
|
||||
"DIALOG": {
|
||||
|
@ -1291,6 +1291,7 @@
|
||||
"MEMBERCHANGED": "Zmieniono managera.",
|
||||
"SETPRIMARY": "Ustawiono domenę podstawową.",
|
||||
"DELETED": "Organizacja została usunięta pomyślnie",
|
||||
"DEFAULTORGNOTFOUND": "Nie znaleziono organizacji domyślnej",
|
||||
"ORG_WAS_DELETED": "Organizacja została usunięta."
|
||||
},
|
||||
"DIALOG": {
|
||||
|
@ -1293,6 +1293,7 @@
|
||||
"MEMBERCHANGED": "Gerente alterado.",
|
||||
"SETPRIMARY": "Domínio principal definido.",
|
||||
"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."
|
||||
},
|
||||
"DIALOG": {
|
||||
|
@ -1335,6 +1335,7 @@
|
||||
"MEMBERCHANGED": "Менеджер изменён.",
|
||||
"SETPRIMARY": "Установлен основной домен.",
|
||||
"DELETED": "Организация успешно удалена",
|
||||
"DEFAULTORGNOTFOUND": "Организация по умолчанию не найдена",
|
||||
"ORG_WAS_DELETED": "Организация удалена."
|
||||
},
|
||||
"DIALOG": {
|
||||
|
@ -1291,6 +1291,7 @@
|
||||
"MEMBERCHANGED": "管理者以改变。",
|
||||
"SETPRIMARY": "已设为主域名。",
|
||||
"DELETED": "成功删除的组织",
|
||||
"DEFAULTORGNOTFOUND": "未找到默认组织",
|
||||
"ORG_WAS_DELETED": "组织被删除"
|
||||
},
|
||||
"DIALOG": {
|
||||
|
@ -57,18 +57,6 @@ spec:
|
||||
selector:
|
||||
app: cockroachdb
|
||||
---
|
||||
apiVersion: policy/v1beta1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: cockroachdb-budget
|
||||
labels:
|
||||
app: cockroachdb
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: cockroachdb
|
||||
maxUnavailable: 1
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
|
@ -7,7 +7,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
client.knative.dev/user-image: ghcr.io/zitadel/zitadel:stable
|
||||
client.knative.dev/user-image: ghcr.io/zitadel/zitadel:latest
|
||||
creationTimestamp: null
|
||||
spec:
|
||||
containerConcurrency: 0
|
||||
@ -28,7 +28,7 @@ spec:
|
||||
value: "80"
|
||||
- name: ZITADEL_EXTERNALDOMAIN
|
||||
value: zitadel.default.127.0.0.1.sslip.io
|
||||
image: ghcr.io/zitadel/zitadel:stable
|
||||
image: ghcr.io/zitadel/zitadel:latest
|
||||
name: user-container
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
|
@ -68,6 +68,19 @@ https://github.com/zitadel/actions/blob/main/examples/custom_roles.js
|
||||
|
||||
</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
|
||||
|
||||
Append attributes returned on SAML requests.
|
||||
|
@ -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
|
||||
- `projectId` *string*
|
||||
- `projectName` *string*
|
||||
- `getOrgMetadata()` [*metadataResult*](#metadata-result)
|
||||
Get the metadata of the organization where the user was granted
|
@ -1,23 +1,78 @@
|
||||
---
|
||||
title: Identity Brokering in ZITADEL
|
||||
title: 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.
|
||||
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.
|
||||
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.
|
||||
|
||||

|
||||

|
||||
|
||||
## 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.
|
||||
ZITADEL also provides templates to configure generic identity providers, which don't have templates.
|
||||
## Is single-sign-on (SSO) the same as identity brokering?
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
## 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)
|
||||
|
@ -9,9 +9,13 @@ import CustomLoginPolicy from './_custom_login_policy.mdx';
|
||||
import IDPsOverview from './_idps_overview.mdx';
|
||||
import Activate from './_activate.mdx';
|
||||
import TestSetup from './_test_setup.mdx';
|
||||
import { ResponsivePlayer } from "../../../../src/components/player";
|
||||
|
||||
<Intro provider="Google"/>
|
||||
|
||||
<ResponsivePlayer playing controls url='https://www.youtube.com/watch?v=wg-ee-EnHdE' />
|
||||
|
||||
|
||||
## Open the Google Identity Provider Template
|
||||
|
||||
<IDPsOverview templates="Google"/>
|
||||
|
BIN
docs/static/img/concepts/features/domain-discovery.png
vendored
Normal file
BIN
docs/static/img/concepts/features/domain-discovery.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
BIN
docs/static/img/concepts/features/identity-brokering.png
vendored
Normal file
BIN
docs/static/img/concepts/features/identity-brokering.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
BIN
docs/static/img/guides/identity_brokering.png
vendored
BIN
docs/static/img/guides/identity_brokering.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 272 KiB |
@ -1,6 +1,7 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
@ -77,6 +78,21 @@ func UserMetadataListFromSlice(c *actions.FieldConfig, metadata []query.UserMeta
|
||||
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 {
|
||||
var value interface{}
|
||||
if !json.Valid(val) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
@ -44,6 +45,8 @@ type userGrant struct {
|
||||
|
||||
ProjectId string
|
||||
ProjectName string
|
||||
|
||||
GetOrgMetadata func(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 {
|
||||
return c.Runtime.ToValue(nil)
|
||||
}
|
||||
orgMetadata := make(map[string]goja.Value)
|
||||
grantList := &userGrantList{
|
||||
Count: userGrants.Count,
|
||||
Sequence: userGrants.Sequence,
|
||||
@ -84,16 +88,24 @@ func UserGrantsFromQuery(c *actions.FieldConfig, userGrants *query.UserGrants) g
|
||||
UserGrantResourceOwnerName: grant.OrgName,
|
||||
ProjectId: grant.ProjectID,
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
return c.Runtime.ToValue(nil)
|
||||
}
|
||||
orgMetadata := make(map[string]goja.Value)
|
||||
grantList := &userGrantList{
|
||||
Count: uint64(len(userGrants)),
|
||||
Grants: make([]*userGrant, len(userGrants)),
|
||||
@ -114,6 +126,13 @@ func UserGrantsFromSlice(c *actions.FieldConfig, userGrants []query.UserGrant) g
|
||||
UserGrantResourceOwnerName: grant.OrgName,
|
||||
ProjectId: grant.ProjectID,
|
||||
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]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ func (s *Server) ResendMyEmailVerification(ctx context.Context, _ *auth_pb.Resen
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -473,7 +473,7 @@ func (s *Server) ResendHumanInitialization(ctx context.Context, req *mgmt_pb.Res
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -487,7 +487,7 @@ func (s *Server) ResendHumanEmailVerification(ctx context.Context, req *mgmt_pb.
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -590,7 +590,7 @@ func (s *Server) SendHumanResetPasswordNotification(ctx context.Context, req *mg
|
||||
if err != nil {
|
||||
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 {
|
||||
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) {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyDetails, err := machineKey.Detail()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
// Return key details only if the pubkey wasn't supplied, otherwise the user already has
|
||||
// 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{
|
||||
KeyId: machineKey.KeyID,
|
||||
|
@ -237,6 +237,7 @@ func AddMachineKeyRequestToCommand(req *mgmt_pb.AddMachineKeyRequest, resourceOw
|
||||
},
|
||||
ExpirationDate: expDate,
|
||||
Type: authn.KeyTypeToDomain(req.Type),
|
||||
PublicKey: req.PublicKey,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -490,25 +490,16 @@ func (o *OPStorage) userinfoFlows(ctx context.Context, user *query.User, userGra
|
||||
return object.UserMetadataListFromQuery(c, metadata)
|
||||
}
|
||||
}),
|
||||
actions.SetFields("grants", func(c *actions.FieldConfig) interface{} {
|
||||
return object.UserGrantsFromQuery(c, userGrants)
|
||||
}),
|
||||
actions.SetFields("grants",
|
||||
func(c *actions.FieldConfig) interface{} {
|
||||
return object.UserGrantsFromQuery(ctx, o.query, c, userGrants)
|
||||
},
|
||||
),
|
||||
),
|
||||
actions.SetFields("org",
|
||||
actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
|
||||
return func(goja.FunctionCall) goja.Value {
|
||||
metadata, err := o.query.SearchOrgMetadata(
|
||||
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)
|
||||
return object.GetOrganizationMetadata(ctx, o.query, c, user.ResourceOwner)
|
||||
}
|
||||
}),
|
||||
),
|
||||
@ -714,24 +705,13 @@ func (o *OPStorage) privateClaimsFlows(ctx context.Context, userID string, userG
|
||||
}
|
||||
}),
|
||||
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("getMetadata", func(c *actions.FieldConfig) interface{} {
|
||||
return func(goja.FunctionCall) goja.Value {
|
||||
metadata, err := o.query.SearchOrgMetadata(
|
||||
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)
|
||||
return object.GetOrganizationMetadata(ctx, o.query, c, user.ResourceOwner)
|
||||
}
|
||||
}),
|
||||
),
|
||||
|
76
internal/api/oidc/server_integration_test.go
Normal file
76
internal/api/oidc/server_integration_test.go
Normal 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
|
||||
}
|
@ -252,24 +252,13 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user
|
||||
}
|
||||
}),
|
||||
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("getMetadata", func(c *actions.FieldConfig) interface{} {
|
||||
return func(goja.FunctionCall) goja.Value {
|
||||
metadata, err := s.query.SearchOrgMetadata(
|
||||
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)
|
||||
return object.GetOrganizationMetadata(ctx, s.query, c, qu.User.ResourceOwner)
|
||||
}
|
||||
}),
|
||||
),
|
||||
|
@ -246,24 +246,13 @@ func (p *Storage) getCustomAttributes(ctx context.Context, user *query.User, use
|
||||
}
|
||||
}),
|
||||
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("getMetadata", func(c *actions.FieldConfig) interface{} {
|
||||
return func(goja.FunctionCall) goja.Value {
|
||||
metadata, err := p.query.SearchOrgMetadata(
|
||||
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)
|
||||
return object.GetOrganizationMetadata(ctx, p.query, c, user.ResourceOwner)
|
||||
}
|
||||
}),
|
||||
),
|
||||
|
@ -3,6 +3,8 @@ package login
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||
"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 {
|
||||
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
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@ -38,14 +38,20 @@ type initPasswordData struct {
|
||||
HasSymbol string
|
||||
}
|
||||
|
||||
func InitPasswordLink(origin, userID, code, orgID string) string {
|
||||
return fmt.Sprintf("%s%s?userID=%s&code=%s&orgID=%s", externalLink(origin), EndpointInitPassword, userID, code, orgID)
|
||||
func InitPasswordLink(origin, userID, code, orgID, authRequestID string) string {
|
||||
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) {
|
||||
authReq := l.checkOptionalAuthRequestOfEmailLinks(r)
|
||||
userID := r.FormValue(queryInitPWUserID)
|
||||
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) {
|
||||
@ -94,7 +100,7 @@ func (l *Login) resendPasswordSet(w http.ResponseWriter, r *http.Request, authRe
|
||||
l.renderInitPassword(w, r, authReq, userID, "", err)
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||
@ -44,16 +44,24 @@ type initUserData struct {
|
||||
HasSymbol string
|
||||
}
|
||||
|
||||
func InitUserLink(origin, userID, loginName, code, orgID string, passwordSet bool) string {
|
||||
return fmt.Sprintf("%s%s?userID=%s&loginname=%s&code=%s&orgID=%s&passwordset=%t", externalLink(origin), EndpointInitUser, userID, loginName, code, orgID, passwordSet)
|
||||
func InitUserLink(origin, userID, loginName, code, orgID string, passwordSet bool, authRequestID string) string {
|
||||
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) {
|
||||
authReq := l.checkOptionalAuthRequestOfEmailLinks(r)
|
||||
userID := r.FormValue(queryInitUserUserID)
|
||||
code := r.FormValue(queryInitUserCode)
|
||||
loginName := r.FormValue(queryInitUserLoginName)
|
||||
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) {
|
||||
@ -105,7 +113,7 @@ func (l *Login) resendUserInit(w http.ResponseWriter, r *http.Request, authReq *
|
||||
l.renderInitUser(w, r, authReq, userID, loginName, "", showPassword, err)
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
)
|
||||
@ -27,18 +27,24 @@ type mailVerificationData struct {
|
||||
UserID string
|
||||
}
|
||||
|
||||
func MailVerificationLink(origin, userID, code, orgID string) string {
|
||||
return fmt.Sprintf("%s%s?userID=%s&code=%s&orgID=%s", externalLink(origin), EndpointMailVerification, userID, code, orgID)
|
||||
func MailVerificationLink(origin, userID, code, orgID, authRequestID string) string {
|
||||
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) {
|
||||
authReq := l.checkOptionalAuthRequestOfEmailLinks(r)
|
||||
userID := r.FormValue(queryUserID)
|
||||
code := r.FormValue(queryCode)
|
||||
if code != "" {
|
||||
l.checkMailCode(w, r, nil, userID, code)
|
||||
l.checkMailCode(w, r, authReq, userID, code)
|
||||
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) {
|
||||
@ -61,7 +67,7 @@ func (l *Login) handleMailVerificationCheck(w http.ResponseWriter, r *http.Reque
|
||||
l.checkMailCode(w, r, authReq, data.UserID, data.Code)
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ func (l *Login) handlePasswordReset(w http.ResponseWriter, r *http.Request) {
|
||||
l.renderPasswordResetDone(w, r, authReq, err)
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
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/zerrors"
|
||||
)
|
||||
@ -67,22 +68,6 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
|
||||
if authRequest != nil && authRequest.RequestedOrgID != "" && authRequest.RequestedOrgID != resourceOwner {
|
||||
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,
|
||||
// the setMetadata() function is provided on the pre creation hook, for now,
|
||||
// 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)
|
||||
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 {
|
||||
l.renderRegister(w, r, authRequest, data, err)
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
userGrants, err := l.runPostCreationActions(human.ID, authRequest, r, resourceOwner, domain.FlowTypeInternalAuthentication)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authRequest, err)
|
||||
return
|
||||
@ -128,7 +105,7 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
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 {
|
||||
l.renderRegister(w, r, authRequest, data, err)
|
||||
return
|
||||
|
@ -543,23 +543,19 @@ func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, regis
|
||||
if err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
emailCodeGenerator, err := repo.Query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, repo.UserCodeAlg)
|
||||
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.SetUserInfo(human.ID, human.Username, human.Username, human.DisplayName, "", resourceOwner)
|
||||
request.SelectedIDPConfigID = externalIDP.IDPConfigID
|
||||
request.LinkingUsers = nil
|
||||
err = repo.Command.UserIDPLoginChecked(ctx, request.UserOrgID, request.UserID, request.WithCurrentInfo(info))
|
||||
|
@ -160,6 +160,10 @@ func (s *UserSession) Reducers() []handler.AggregateReducer {
|
||||
Event: user.UserRemovedType,
|
||||
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("external_login_verification", time.Time{}),
|
||||
handler.NewCol("state", domain.UserSessionStateTerminated),
|
||||
handler.NewCol("change_date", event.CreatedAt()),
|
||||
handler.NewCol("sequence", event.Sequence()),
|
||||
},
|
||||
[]handler.Condition{
|
||||
handler.NewCond("instance_id", event.Aggregate().InstanceID),
|
||||
@ -247,16 +253,30 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return handler.NewUpdateStatement(event,
|
||||
[]handler.Column{
|
||||
handler.NewCol("password_verification", time.Time{}),
|
||||
},
|
||||
[]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)),
|
||||
},
|
||||
return handler.NewMultiStatement(event,
|
||||
handler.AddUpdateStatement(
|
||||
[]handler.Column{
|
||||
handler.NewCol("password_verification", event.CreatedAt()),
|
||||
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.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
|
||||
case user.UserV1MFAOTPRemovedType,
|
||||
user.HumanMFAOTPRemovedType,
|
||||
@ -264,6 +284,8 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
return handler.NewUpdateStatement(event,
|
||||
[]handler.Column{
|
||||
handler.NewCol("second_factor_verification", time.Time{}),
|
||||
handler.NewCol("change_date", event.CreatedAt()),
|
||||
handler.NewCol("sequence", event.Sequence()),
|
||||
},
|
||||
[]handler.Condition{
|
||||
handler.NewCond("instance_id", event.Aggregate().InstanceID),
|
||||
@ -277,6 +299,8 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
[]handler.Column{
|
||||
handler.NewCol("external_login_verification", time.Time{}),
|
||||
handler.NewCol("selected_idp_config_id", ""),
|
||||
handler.NewCol("change_date", event.CreatedAt()),
|
||||
handler.NewCol("sequence", event.Sequence()),
|
||||
},
|
||||
[]handler.Condition{
|
||||
handler.NewCond("instance_id", event.Aggregate().InstanceID),
|
||||
@ -289,6 +313,8 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
|
||||
[]handler.Column{
|
||||
handler.NewCol("passwordless_verification", time.Time{}),
|
||||
handler.NewCol("multi_factor_verification", time.Time{}),
|
||||
handler.NewCol("change_date", event.CreatedAt()),
|
||||
handler.NewCol("sequence", event.Sequence()),
|
||||
},
|
||||
[]handler.Condition{
|
||||
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 u.view.DeleteUserSessions(event.Aggregate().ID, event.Aggregate().InstanceID)
|
||||
}), 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:
|
||||
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
|
||||
return u.view.DeleteInstanceUserSessions(event.Aggregate().InstanceID)
|
||||
|
@ -1427,6 +1427,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) {
|
||||
Crypted: []byte("userinit"),
|
||||
},
|
||||
1*time.Hour,
|
||||
"",
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(org.NewMemberAddedEvent(context.Background(),
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"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/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
@ -57,7 +56,13 @@ type AddHuman struct {
|
||||
Passwordless bool
|
||||
ExternalIDP 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 []*AddLink
|
||||
@ -200,6 +205,7 @@ func (c *Commands) AddHumanCommand(human *AddHuman, orgID string, hasher *crypto
|
||||
human.Gender,
|
||||
human.Email.Address,
|
||||
domainPolicy.UserLoginMustBeDomain,
|
||||
"", // no user agent id available
|
||||
)
|
||||
} else {
|
||||
createCmd = user.NewHumanAddedEvent(
|
||||
@ -272,7 +278,7 @@ func (c *Commands) addHumanCommandEmail(ctx context.Context, filter preparation.
|
||||
if err != nil {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if orgID == "" {
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
if human == nil {
|
||||
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) {
|
||||
//nolint:gocognit
|
||||
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) {
|
||||
if err := human.CheckDomainPolicy(domainPolicy); err != nil {
|
||||
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
|
||||
userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel)
|
||||
|
||||
if selfregister {
|
||||
events = append(events, createRegisterHumanEvent(ctx, userAgg, human, domainPolicy.UserLoginMustBeDomain))
|
||||
} else {
|
||||
events = append(events, createAddHumanEvent(ctx, userAgg, human, domainPolicy.UserLoginMustBeDomain))
|
||||
}
|
||||
events = append(events, createAddHumanEvent(ctx, userAgg, human, domainPolicy.UserLoginMustBeDomain))
|
||||
|
||||
for _, link := range links {
|
||||
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 {
|
||||
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 {
|
||||
if human.Email != nil && human.EmailAddress != "" && human.IsEmailVerified {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
if agentID == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing")
|
||||
@ -783,3 +671,53 @@ func humanWriteModelByID(ctx context.Context, filter preparation.FilterToQueryRe
|
||||
err = humanWriteModel.Reduce()
|
||||
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
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ func (c *Commands) ChangeHumanEmail(ctx context.Context, email *domain.Email, em
|
||||
if err != nil {
|
||||
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...)
|
||||
@ -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")
|
||||
}
|
||||
|
||||
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 == "" {
|
||||
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 {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ type HumanEmailWriteModel struct {
|
||||
Code *crypto.CryptoValue
|
||||
CodeCreationDate time.Time
|
||||
CodeExpiry time.Duration
|
||||
AuthRequestID string
|
||||
|
||||
UserState domain.UserState
|
||||
}
|
||||
@ -53,6 +54,7 @@ func (wm *HumanEmailWriteModel) Reduce() error {
|
||||
wm.Code = e.Code
|
||||
wm.CodeCreationDate = e.CreationDate()
|
||||
wm.CodeExpiry = e.Expiry
|
||||
wm.AuthRequestID = e.AuthRequestID
|
||||
case *user.HumanEmailVerifiedEvent:
|
||||
wm.IsEmailVerified = true
|
||||
wm.Code = nil
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
|
||||
func TestCommandSide_ChangeHumanEmail(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@ -39,9 +39,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "invalid email, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@ -59,8 +57,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@ -81,8 +78,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "user not initialized, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -102,6 +98,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
|
||||
user.NewHumanInitialCodeAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
nil, time.Hour*1,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -124,8 +121,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "email not changed, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -161,8 +157,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "verified email changed, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -215,8 +210,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "email verified, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -265,8 +259,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "email verified, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -315,8 +308,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "email changed with code, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -347,6 +339,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -376,7 +369,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
got, err := r.ChangeHumanEmail(tt.args.ctx, tt.args.email, tt.args.secretGenerator)
|
||||
if tt.res.err == nil {
|
||||
@ -394,7 +387,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
|
||||
|
||||
func TestCommandSide_VerifyHumanEmail(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@ -416,9 +409,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@ -432,9 +423,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "code missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@ -448,8 +437,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@ -466,8 +454,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "code not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -499,8 +486,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "invalid code, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -526,6 +512,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -550,8 +537,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "valid code, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -577,6 +563,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -604,7 +591,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
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)
|
||||
if tt.res.err == nil {
|
||||
@ -622,13 +609,14 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
|
||||
|
||||
func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
userID string
|
||||
resourceOwner string
|
||||
secretGenerator crypto.Generator
|
||||
authRequestID string
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
@ -643,9 +631,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@ -658,8 +644,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@ -675,8 +660,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "user not initialized, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -696,6 +680,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
|
||||
user.NewHumanInitialCodeAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
nil, time.Hour*1,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -713,8 +698,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "email already verified, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -750,8 +734,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
|
||||
{
|
||||
name: "new code, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -789,6 +772,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
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 {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
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 {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@ -827,7 +870,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
|
||||
|
||||
func TestCommandSide_EmailVerificationCodeSent(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@ -846,9 +889,7 @@ func TestCommandSide_EmailVerificationCodeSent(t *testing.T) {
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@ -861,8 +902,7 @@ func TestCommandSide_EmailVerificationCodeSent(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@ -878,8 +918,7 @@ func TestCommandSide_EmailVerificationCodeSent(t *testing.T) {
|
||||
{
|
||||
name: "code sent, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -925,7 +964,7 @@ func TestCommandSide_EmailVerificationCodeSent(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
err := r.HumanEmailVerificationCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID)
|
||||
if tt.res.err == nil {
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
// 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 == "" {
|
||||
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 {
|
||||
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...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -19,6 +19,7 @@ type HumanInitCodeWriteModel struct {
|
||||
Code *crypto.CryptoValue
|
||||
CodeCreationDate time.Time
|
||||
CodeExpiry time.Duration
|
||||
AuthRequestID string
|
||||
|
||||
UserState domain.UserState
|
||||
}
|
||||
@ -50,6 +51,7 @@ func (wm *HumanInitCodeWriteModel) Reduce() error {
|
||||
wm.Code = e.Code
|
||||
wm.CodeCreationDate = e.CreationDate()
|
||||
wm.CodeExpiry = e.Expiry
|
||||
wm.AuthRequestID = e.AuthRequestID
|
||||
wm.UserState = domain.UserStateInitial
|
||||
case *user.HumanInitializedCheckSucceededEvent:
|
||||
wm.Code = nil
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
|
||||
func TestCommandSide_ResendInitialMail(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@ -26,6 +26,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
|
||||
email string
|
||||
resourceOwner string
|
||||
secretGenerator crypto.Generator
|
||||
authRequestID string
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
@ -40,9 +41,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@ -55,8 +54,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@ -72,8 +70,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
|
||||
{
|
||||
name: "user not initialized, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -107,8 +104,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
|
||||
{
|
||||
name: "new code email not changed, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -128,6 +124,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
|
||||
user.NewHumanInitialCodeAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
nil, time.Hour*1,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -141,6 +138,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
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{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -182,6 +179,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
|
||||
user.NewHumanInitialCodeAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
nil, time.Hour*1,
|
||||
"authRequestID",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -195,6 +193,63 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
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{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -235,6 +289,62 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
|
||||
user.NewHumanInitialCodeAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
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"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -273,9 +384,9 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
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 {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@ -291,7 +402,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
|
||||
|
||||
func TestCommandSide_VerifyInitCode(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
userPasswordHasher *crypto.Hasher
|
||||
}
|
||||
type args struct {
|
||||
@ -316,9 +427,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@ -332,9 +441,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
|
||||
{
|
||||
name: "code missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@ -348,8 +455,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@ -366,8 +472,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
|
||||
{
|
||||
name: "code not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -399,8 +504,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
|
||||
{
|
||||
name: "invalid code, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -426,6 +530,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -450,8 +555,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
|
||||
{
|
||||
name: "valid code, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -477,6 +581,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -506,8 +611,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
|
||||
{
|
||||
name: "valid code with password, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -536,6 +640,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -582,8 +687,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
|
||||
{
|
||||
name: "valid code with password and userAgentID, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -612,6 +716,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -660,7 +765,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
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)
|
||||
@ -676,7 +781,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
|
||||
|
||||
func TestCommandSide_InitCodeSent(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@ -695,9 +800,7 @@ func TestCommandSide_InitCodeSent(t *testing.T) {
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@ -710,8 +813,7 @@ func TestCommandSide_InitCodeSent(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@ -727,8 +829,7 @@ func TestCommandSide_InitCodeSent(t *testing.T) {
|
||||
{
|
||||
name: "code sent, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -763,7 +864,7 @@ func TestCommandSide_InitCodeSent(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
err := r.HumanInitCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID)
|
||||
if tt.res.err == nil {
|
||||
|
@ -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
|
||||
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 == "" {
|
||||
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 {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import (
|
||||
|
||||
func TestCommandSide_SetOneTimePassword(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
userPasswordHasher *crypto.Hasher
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
@ -46,9 +46,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@ -61,8 +59,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@ -78,8 +75,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
|
||||
{
|
||||
name: "missing permission, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -121,8 +117,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
|
||||
{
|
||||
name: "change password onetime, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -184,8 +179,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
|
||||
{
|
||||
name: "change password no one time, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -248,7 +242,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
userPasswordHasher: tt.fields.userPasswordHasher,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
@ -268,7 +262,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
|
||||
|
||||
func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
userEncryption crypto.EncryptionAlgorithm
|
||||
userPasswordHasher *crypto.Hasher
|
||||
}
|
||||
@ -293,9 +287,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@ -308,9 +300,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
|
||||
{
|
||||
name: "password missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@ -324,8 +314,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@ -342,8 +331,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
|
||||
{
|
||||
name: "code not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -376,8 +364,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
|
||||
{
|
||||
name: "invalid code, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -404,6 +391,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
|
||||
},
|
||||
time.Hour*1,
|
||||
domain.NotificationTypeEmail,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -424,8 +412,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
|
||||
{
|
||||
name: "set password, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -457,6 +444,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
|
||||
},
|
||||
time.Hour*1,
|
||||
domain.NotificationTypeEmail,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -500,8 +488,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
|
||||
{
|
||||
name: "set password with userAgentID, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -533,6 +520,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
|
||||
},
|
||||
time.Hour*1,
|
||||
domain.NotificationTypeEmail,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -578,7 +566,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
userPasswordHasher: tt.fields.userPasswordHasher,
|
||||
userEncryption: tt.fields.userEncryption,
|
||||
}
|
||||
@ -915,7 +903,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
||||
|
||||
func TestCommandSide_RequestSetPassword(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@ -923,6 +911,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
|
||||
resourceOwner string
|
||||
notifyType domain.NotificationType
|
||||
secretGenerator crypto.Generator
|
||||
authRequestID string
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
@ -937,9 +926,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@ -952,8 +939,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@ -969,8 +955,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
|
||||
{
|
||||
name: "user initial, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -990,6 +975,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
|
||||
user.NewHumanInitialCodeAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
nil, time.Hour*1,
|
||||
"",
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
@ -1018,8 +1004,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
|
||||
{
|
||||
name: "new code, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -1055,6 +1040,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
|
||||
},
|
||||
time.Hour*1,
|
||||
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 {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
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 {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@ -1093,7 +1136,7 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
|
||||
|
||||
func TestCommandSide_PasswordCodeSent(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@ -1112,9 +1155,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@ -1127,8 +1168,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@ -1144,8 +1184,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
|
||||
{
|
||||
name: "code sent, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -1186,7 +1225,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
err := r.PasswordCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID)
|
||||
if tt.res.err == nil {
|
||||
@ -1201,7 +1240,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
|
||||
|
||||
func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
userPasswordHasher *crypto.Hasher
|
||||
}
|
||||
type args struct {
|
||||
@ -1224,9 +1263,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@ -1240,9 +1277,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
{
|
||||
name: "password missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@ -1256,8 +1291,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
{
|
||||
name: "login policy not found, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectFilter(),
|
||||
),
|
||||
@ -1275,8 +1309,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
{
|
||||
name: "login policy login password not allowed, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewLoginPolicyAddedEvent(context.Background(),
|
||||
@ -1316,8 +1349,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewLoginPolicyAddedEvent(context.Background(),
|
||||
@ -1358,8 +1390,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
{
|
||||
name: "user locked, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewLoginPolicyAddedEvent(context.Background(),
|
||||
@ -1420,8 +1451,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
{
|
||||
name: "existing password empty, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewLoginPolicyAddedEvent(context.Background(),
|
||||
@ -1478,8 +1508,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
{
|
||||
name: "password not matching lockout policy not relevant, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
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",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewLoginPolicyAddedEvent(context.Background(),
|
||||
@ -1653,8 +1681,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
{
|
||||
name: "check password, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewLoginPolicyAddedEvent(context.Background(),
|
||||
@ -1734,8 +1761,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
{
|
||||
name: "check password, ok, updated hash",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewLoginPolicyAddedEvent(context.Background(),
|
||||
@ -1820,8 +1846,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
{
|
||||
name: "check password ok, locked in the mean time",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewLoginPolicyAddedEvent(context.Background(),
|
||||
@ -1900,8 +1925,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
{
|
||||
name: "regression test old version event",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewLoginPolicyAddedEvent(context.Background(),
|
||||
@ -1996,7 +2020,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
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)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
@ -78,6 +79,12 @@ func (key *MachineKey) valid() (err error) {
|
||||
if err := key.content(); err != nil {
|
||||
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)
|
||||
return err
|
||||
}
|
||||
|
@ -18,6 +18,16 @@ import (
|
||||
"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) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
@ -145,7 +155,7 @@ func TestCommands_AddMachineKey(t *testing.T) {
|
||||
"key1",
|
||||
domain.AuthNKeyTypeJSON,
|
||||
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,
|
||||
ExpirationDate: time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC),
|
||||
PublicKey: []byte("public"),
|
||||
PublicKey: []byte(fakePubkey),
|
||||
},
|
||||
},
|
||||
res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
key: true,
|
||||
key: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -194,7 +204,7 @@ func TestCommands_AddMachineKey(t *testing.T) {
|
||||
"key1",
|
||||
domain.AuthNKeyTypeJSON,
|
||||
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",
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
ExpirationDate: time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC),
|
||||
PublicKey: []byte("public"),
|
||||
PublicKey: []byte(fakePubkey),
|
||||
},
|
||||
},
|
||||
res{
|
||||
want: &domain.ObjectDetails{
|
||||
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 {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
if tt.res.key {
|
||||
assert.NotEqual(t, "", tt.args.key.PrivateKey)
|
||||
}
|
||||
receivedKey := len(tt.args.key.PrivateKey) > 0
|
||||
assert.Equal(t, tt.res.key, receivedKey)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1797,6 +1797,7 @@ func TestExistsUser(t *testing.T) {
|
||||
domain.GenderFemale,
|
||||
"support@zitadel.com",
|
||||
true,
|
||||
"userAgentID",
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
|
@ -1838,7 +1838,7 @@ func TestCommands_verifyUserEmailWithGenerator(t *testing.T) {
|
||||
|
||||
func TestCommands_NewUserEmailEvents(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
userID string
|
||||
@ -1852,7 +1852,7 @@ func TestCommands_NewUserEmailEvents(t *testing.T) {
|
||||
{
|
||||
name: "missing userID",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
userID: "",
|
||||
@ -1862,7 +1862,7 @@ func TestCommands_NewUserEmailEvents(t *testing.T) {
|
||||
{
|
||||
name: "not found",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t, expectFilter()),
|
||||
eventstore: expectEventstore(expectFilter()),
|
||||
},
|
||||
args: args{
|
||||
userID: "user1",
|
||||
@ -1872,8 +1872,7 @@ func TestCommands_NewUserEmailEvents(t *testing.T) {
|
||||
{
|
||||
name: "user not initialized",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -1893,6 +1892,7 @@ func TestCommands_NewUserEmailEvents(t *testing.T) {
|
||||
user.NewHumanInitialCodeAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
nil, time.Hour*1,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -1907,7 +1907,7 @@ func TestCommands_NewUserEmailEvents(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
_, err := c.NewUserEmailEvents(context.Background(), tt.args.userID)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
|
@ -131,8 +131,10 @@ func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-7yiox1isql", "Errors.User.AlreadyExisting")
|
||||
}
|
||||
// check for permission to create user on resourceOwner
|
||||
if err := c.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, human.ID); err != nil {
|
||||
return err
|
||||
if !human.Register {
|
||||
if err := c.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, human.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// add resourceowner for the events with the aggregate
|
||||
existingHuman.ResourceOwner = resourceOwner
|
||||
@ -159,6 +161,7 @@ func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human
|
||||
human.Gender,
|
||||
human.Email.Address,
|
||||
domainPolicy.UserLoginMustBeDomain,
|
||||
human.UserAgentID,
|
||||
)
|
||||
} else {
|
||||
createCmd = user.NewHumanAddedEvent(
|
||||
|
@ -232,6 +232,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
|
||||
domain.GenderUnspecified,
|
||||
"email@test.ch",
|
||||
true,
|
||||
"userAgentID",
|
||||
),
|
||||
user.NewHumanInitialCodeAddedEvent(context.Background(),
|
||||
&userAgg.Aggregate,
|
||||
@ -242,6 +243,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
|
||||
Crypted: []byte("userinit"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"authRequestID",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -261,6 +263,8 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
|
||||
},
|
||||
PreferredLanguage: language.English,
|
||||
Register: true,
|
||||
UserAgentID: "userAgentID",
|
||||
AuthRequestID: "authRequestID",
|
||||
},
|
||||
secretGenerator: GetMockSecretGenerator(t),
|
||||
allowInitMail: true,
|
||||
@ -344,6 +348,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
|
||||
Crypted: []byte("userinit"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -414,6 +419,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
|
||||
Crypted: []byte("userinit"),
|
||||
},
|
||||
1*time.Hour,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -1031,6 +1037,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
|
||||
Crypted: []byte("userinit"),
|
||||
},
|
||||
1*time.Hour,
|
||||
"",
|
||||
),
|
||||
user.NewHumanPhoneVerifiedEvent(
|
||||
context.Background(),
|
||||
@ -1174,6 +1181,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
|
||||
Crypted: []byte("userinit"),
|
||||
},
|
||||
1*time.Hour,
|
||||
"",
|
||||
),
|
||||
user.NewMetadataSetEvent(
|
||||
context.Background(),
|
||||
@ -1993,6 +2001,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
||||
user.NewHumanInitialCodeAddedEvent(context.Background(),
|
||||
&userAgg.Aggregate,
|
||||
nil, time.Hour*1,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -167,6 +167,7 @@ func TestCommandSide_userExistsWriteModel(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"authRequestID",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -225,6 +226,7 @@ func TestCommandSide_userExistsWriteModel(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"authRequestID",
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
@ -280,6 +282,7 @@ func TestCommandSide_userExistsWriteModel(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"authRequestID",
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
@ -69,7 +70,7 @@ func TestCommands_RequestPasswordReset(t *testing.T) {
|
||||
),
|
||||
eventFromEventPusher(
|
||||
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(
|
||||
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(
|
||||
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(
|
||||
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, ""),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
|
||||
func TestCommandSide_LockUserV2(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type (
|
||||
@ -40,9 +40,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
@ -58,8 +56,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
@ -77,8 +74,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
|
||||
{
|
||||
name: "user already locked, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -116,8 +112,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
|
||||
{
|
||||
name: "user already locked, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
@ -151,8 +146,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
|
||||
{
|
||||
name: "lock user, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -190,8 +184,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
|
||||
{
|
||||
name: "lock user, no permission",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -224,8 +217,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
|
||||
{
|
||||
name: "lock user machine, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
@ -260,7 +252,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
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) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type (
|
||||
@ -301,9 +293,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
@ -319,8 +309,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
@ -338,8 +327,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
|
||||
{
|
||||
name: "user already active, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -372,8 +360,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
|
||||
{
|
||||
name: "user already active, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
@ -400,8 +387,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
|
||||
{
|
||||
name: "unlock user, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -443,8 +429,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
|
||||
{
|
||||
name: "unlock user, no permission",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -481,8 +466,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
|
||||
{
|
||||
name: "unlock user machine, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
@ -521,7 +505,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
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) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type (
|
||||
@ -562,9 +546,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
@ -580,8 +562,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
@ -599,8 +580,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
|
||||
{
|
||||
name: "user initial, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -620,6 +600,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
|
||||
user.NewHumanInitialCodeAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
nil, time.Hour*1,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -639,8 +620,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
|
||||
{
|
||||
name: "user already inactive, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -678,8 +658,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
|
||||
{
|
||||
name: "deactivate user, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -722,8 +701,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
|
||||
{
|
||||
name: "deactivate user, no permission",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -761,8 +739,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
|
||||
{
|
||||
name: "user machine already inactive, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
@ -796,8 +773,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
|
||||
{
|
||||
name: "deactivate user machine, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
@ -832,7 +808,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
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) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type (
|
||||
@ -873,9 +849,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
@ -891,8 +865,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
@ -910,8 +883,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
|
||||
{
|
||||
name: "user already active, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -944,8 +916,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
|
||||
{
|
||||
name: "user machine already active, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
@ -974,8 +945,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
|
||||
{
|
||||
name: "reactivate user, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -1017,8 +987,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
|
||||
{
|
||||
name: "reactivate user, no permission",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -1055,8 +1024,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
|
||||
{
|
||||
name: "reactivate user machine, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
@ -1095,7 +1063,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
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) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type (
|
||||
@ -1138,9 +1106,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
|
||||
{
|
||||
name: "userid missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
@ -1156,8 +1122,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
@ -1175,8 +1140,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
|
||||
{
|
||||
name: "user removed, notfound error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -1217,8 +1181,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
|
||||
{
|
||||
name: "remove user, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -1269,8 +1232,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
|
||||
{
|
||||
name: "remove user, no permission",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@ -1308,8 +1270,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
|
||||
{
|
||||
name: "user machine already removed, notfound error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
@ -1346,8 +1307,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
|
||||
{
|
||||
name: "remove user machine, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
@ -1395,7 +1355,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.RemoveUserV2(tt.args.ctx, tt.args.userID, tt.args.cascadingMemberships, tt.args.grantIDs...)
|
||||
|
@ -169,7 +169,7 @@ func (u *userNotifier) reduceInitCodeAdded(event eventstore.Event) (*handler.Sta
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@ -226,7 +226,7 @@ func (u *userNotifier) reduceEmailCodeAdded(event eventstore.Event) (*handler.St
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@ -285,7 +285,7 @@ func (u *userNotifier) reducePasswordCodeAdded(event eventstore.Event) (*handler
|
||||
if e.NotificationType == domain.NotificationTypeSms {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ const (
|
||||
externalSecure = false
|
||||
externalProtocol = "http"
|
||||
defaultOTPEmailTemplate = "/otp/verify?loginName={{.LoginName}}&code={{.Code}}"
|
||||
authRequestID = "authRequestID"
|
||||
)
|
||||
|
||||
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) {
|
||||
givenTemplate := "{{.URL}}"
|
||||
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{
|
||||
Recipients: []string{lastEmail},
|
||||
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) {
|
||||
givenTemplate := "{{.URL}}"
|
||||
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{
|
||||
Recipients: []string{lastEmail},
|
||||
Subject: expectMailSubject,
|
||||
@ -196,6 +197,46 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) {
|
||||
},
|
||||
}, 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?
|
||||
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) {
|
||||
givenTemplate := "{{.URL}}"
|
||||
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{
|
||||
Recipients: []string{lastEmail},
|
||||
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) {
|
||||
givenTemplate := "{{.URL}}"
|
||||
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{
|
||||
Recipients: []string{lastEmail},
|
||||
Subject: expectMailSubject,
|
||||
@ -378,6 +419,48 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) {
|
||||
},
|
||||
}, 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",
|
||||
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) {
|
||||
givenTemplate := "{{.URL}}"
|
||||
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{
|
||||
Recipients: []string{lastEmail},
|
||||
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) {
|
||||
givenTemplate := "{{.URL}}"
|
||||
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{
|
||||
Recipients: []string{lastEmail},
|
||||
Subject: expectMailSubject,
|
||||
@ -597,6 +680,48 @@ func Test_userNotifier_reducePasswordCodeAdded(t *testing.T) {
|
||||
},
|
||||
}, 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",
|
||||
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
|
||||
|
@ -10,10 +10,10 @@ import (
|
||||
"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
|
||||
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 {
|
||||
var buf strings.Builder
|
||||
if err := domain.RenderConfirmURLTemplate(&buf, urlTmpl, user.ID, code, user.ResourceOwner); err != nil {
|
||||
|
@ -15,10 +15,11 @@ import (
|
||||
|
||||
func TestNotify_SendEmailVerificationCode(t *testing.T) {
|
||||
type args struct {
|
||||
user *query.NotifyUser
|
||||
origin string
|
||||
code string
|
||||
urlTmpl string
|
||||
user *query.NotifyUser
|
||||
origin string
|
||||
code string
|
||||
urlTmpl string
|
||||
authRequestID string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -33,12 +34,13 @@ func TestNotify_SendEmailVerificationCode(t *testing.T) {
|
||||
ID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
origin: "https://example.com",
|
||||
code: "123",
|
||||
urlTmpl: "",
|
||||
origin: "https://example.com",
|
||||
code: "123",
|
||||
urlTmpl: "",
|
||||
authRequestID: "authRequestID",
|
||||
},
|
||||
want: ¬ifyResult{
|
||||
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"},
|
||||
messageType: domain.VerifyEmailMessageType,
|
||||
allowUnverifiedNotificationChannel: true,
|
||||
@ -51,9 +53,10 @@ func TestNotify_SendEmailVerificationCode(t *testing.T) {
|
||||
ID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
origin: "https://example.com",
|
||||
code: "123",
|
||||
urlTmpl: "{{",
|
||||
origin: "https://example.com",
|
||||
code: "123",
|
||||
urlTmpl: "{{",
|
||||
authRequestID: "authRequestID",
|
||||
},
|
||||
want: ¬ifyResult{},
|
||||
wantErr: zerrors.ThrowInvalidArgument(nil, "DOMAIN-oGh5e", "Errors.User.InvalidURLTemplate"),
|
||||
@ -65,9 +68,10 @@ func TestNotify_SendEmailVerificationCode(t *testing.T) {
|
||||
ID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
origin: "https://example.com",
|
||||
code: "123",
|
||||
urlTmpl: "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}",
|
||||
origin: "https://example.com",
|
||||
code: "123",
|
||||
urlTmpl: "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}",
|
||||
authRequestID: "authRequestID",
|
||||
},
|
||||
want: ¬ifyResult{
|
||||
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 {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
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)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
|
@ -9,8 +9,8 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
)
|
||||
|
||||
func (notify Notify) SendUserInitCode(ctx context.Context, user *query.NotifyUser, code string) error {
|
||||
url := login.InitUserLink(http_utils.ComposedOrigin(ctx), user.ID, user.PreferredLoginName, code, user.ResourceOwner, user.PasswordSet)
|
||||
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, authRequestID)
|
||||
args := make(map[string]interface{})
|
||||
args["Code"] = code
|
||||
return notify(url, args, domain.InitCodeMessageType, true)
|
||||
|
@ -10,10 +10,10 @@ import (
|
||||
"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
|
||||
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 {
|
||||
var buf strings.Builder
|
||||
if err := domain.RenderConfirmURLTemplate(&buf, urlTmpl, user.ID, code, user.ResourceOwner); err != nil {
|
||||
|
@ -157,6 +157,8 @@ type HumanRegisteredEvent struct {
|
||||
Secret *crypto.CryptoValue `json:"secret,omitempty"` // legacy
|
||||
EncodedHash string `json:"encodedHash,omitempty"`
|
||||
ChangeRequired bool `json:"changeRequired,omitempty"`
|
||||
|
||||
UserAgentID string `json:"userAgentID,omitempty"`
|
||||
}
|
||||
|
||||
func (e *HumanRegisteredEvent) Payload() interface{} {
|
||||
@ -208,6 +210,7 @@ func NewHumanRegisteredEvent(
|
||||
gender domain.Gender,
|
||||
emailAddress domain.EmailAddress,
|
||||
userLoginMustBeDomain bool,
|
||||
userAgentID string,
|
||||
) *HumanRegisteredEvent {
|
||||
return &HumanRegisteredEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
@ -224,6 +227,7 @@ func NewHumanRegisteredEvent(
|
||||
Gender: gender,
|
||||
EmailAddress: emailAddress,
|
||||
userLoginMustBeDomain: userLoginMustBeDomain,
|
||||
UserAgentID: userAgentID,
|
||||
}
|
||||
}
|
||||
|
||||
@ -244,6 +248,7 @@ type HumanInitialCodeAddedEvent struct {
|
||||
Code *crypto.CryptoValue `json:"code,omitempty"`
|
||||
Expiry time.Duration `json:"expiry,omitempty"`
|
||||
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
|
||||
AuthRequestID string `json:"authRequestID,omitempty"`
|
||||
}
|
||||
|
||||
func (e *HumanInitialCodeAddedEvent) Payload() interface{} {
|
||||
@ -263,6 +268,7 @@ func NewHumanInitialCodeAddedEvent(
|
||||
aggregate *eventstore.Aggregate,
|
||||
code *crypto.CryptoValue,
|
||||
expiry time.Duration,
|
||||
authRequestID string,
|
||||
) *HumanInitialCodeAddedEvent {
|
||||
return &HumanInitialCodeAddedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
@ -273,6 +279,7 @@ func NewHumanInitialCodeAddedEvent(
|
||||
Code: code,
|
||||
Expiry: expiry,
|
||||
TriggeredAtOrigin: http.ComposedOrigin(ctx),
|
||||
AuthRequestID: authRequestID,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,6 +126,8 @@ type HumanEmailCodeAddedEvent struct {
|
||||
URLTemplate string `json:"url_template,omitempty"`
|
||||
CodeReturned bool `json:"code_returned,omitempty"`
|
||||
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
|
||||
// AuthRequest is only used in V1 Login UI
|
||||
AuthRequestID string `json:"authRequestID,omitempty"`
|
||||
}
|
||||
|
||||
func (e *HumanEmailCodeAddedEvent) Payload() interface{} {
|
||||
@ -145,8 +147,19 @@ func NewHumanEmailCodeAddedEvent(
|
||||
aggregate *eventstore.Aggregate,
|
||||
code *crypto.CryptoValue,
|
||||
expiry time.Duration,
|
||||
authRequestID string,
|
||||
) *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(
|
||||
|
@ -87,6 +87,8 @@ type HumanPasswordCodeAddedEvent struct {
|
||||
URLTemplate string `json:"url_template,omitempty"`
|
||||
CodeReturned bool `json:"code_returned,omitempty"`
|
||||
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
|
||||
// AuthRequest is only used in V1 Login UI
|
||||
AuthRequestID string `json:"authRequestID,omitempty"`
|
||||
}
|
||||
|
||||
func (e *HumanPasswordCodeAddedEvent) Payload() interface{} {
|
||||
@ -107,8 +109,20 @@ func NewHumanPasswordCodeAddedEvent(
|
||||
code *crypto.CryptoValue,
|
||||
expiry time.Duration,
|
||||
notificationType domain.NotificationType,
|
||||
authRequestID string,
|
||||
) *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(
|
||||
|
@ -111,6 +111,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: Машинният ключ не е намерен
|
||||
AlreadyExisting: Машинният ключ вече съществува
|
||||
Invalid: Публичният ключ не е валиден RSA публичен ключ във формат PKIX с PEM кодиране
|
||||
Secret:
|
||||
NotExisting: Тайната не съществува
|
||||
Invalid: Тайната е невалидна
|
||||
|
@ -109,6 +109,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: Klíč stroje nenalezen
|
||||
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:
|
||||
NotExisting: Tajemství neexistuje
|
||||
Invalid: Tajemství je neplatné
|
||||
|
@ -109,6 +109,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: Maschinen Schlüssel nicht gefunden
|
||||
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:
|
||||
NotExisting: Secret existiert nicht
|
||||
Invalid: Secret ist ungültig
|
||||
|
@ -109,6 +109,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: Machine key not found
|
||||
AlreadyExisting: Machine key already existing
|
||||
Invalid: Public key is not a valid RSA public key in PKIX format with PEM encoding
|
||||
Secret:
|
||||
NotExisting: Secret doesn't exist
|
||||
Invalid: Secret is invalid
|
||||
|
@ -109,6 +109,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: Clave de máquina no encontrada
|
||||
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:
|
||||
NotExisting: El secreto no existe
|
||||
Invalid: El secret no es válido
|
||||
|
@ -109,6 +109,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: Clé de la machine non trouvée
|
||||
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:
|
||||
NotExisting: Secret n'existe pas
|
||||
Invalid: Secret n'est pas valide
|
||||
|
@ -109,6 +109,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: Chiave macchina non trovato
|
||||
AlreadyExisting: Chiave macchina già esistente
|
||||
Invalid: La chiave pubblica non è una chiave pubblica RSA valida in formato PKIX con codifica PEM
|
||||
Secret:
|
||||
NotExisting: Secret non esiste
|
||||
Invalid: Secret non è valido
|
||||
|
@ -102,6 +102,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: マシーンキーが見つかりません
|
||||
AlreadyExisting: すでに存在しているマシーンキーです
|
||||
Invalid: 公開キーは、PEM エンコードを使用した PKIX 形式の有効な RSA 公開キーではありません
|
||||
Secret:
|
||||
NotExisting: シークレットは存在しません
|
||||
Invalid: 無効なシークレットです
|
||||
|
@ -109,6 +109,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: Machine key не е пронајден
|
||||
AlreadyExisting: Machine key веќе постои
|
||||
Invalid: Јавниот клуч не е валиден јавен клуч RSA во формат PKIX со PEM кодирање
|
||||
Secret:
|
||||
NotExisting: Тајната не постои
|
||||
Invalid: Тајната е невалидна
|
||||
|
@ -108,6 +108,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: Machine sleutel niet gevonden
|
||||
AlreadyExisting: Machine sleutel al bestaand
|
||||
Invalid: De openbare sleutel is geen geldige openbare RSA-sleutel in PKIX-indeling met PEM-codering
|
||||
Secret:
|
||||
NotExisting: Geheim bestaat niet
|
||||
Invalid: Geheim is ongeldig
|
||||
|
@ -109,6 +109,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: Klucz maszyny nie znaleziony
|
||||
AlreadyExisting: Klucz maszyny już istnieje
|
||||
Invalid: Klucz publiczny nie jest prawidłowym kluczem publicznym RSA w formacie PKIX z kodowaniem PEM
|
||||
Secret:
|
||||
NotExisting: Sekret nie istnieje
|
||||
Invalid: Sekret jest nieprawidłowy
|
||||
|
@ -109,6 +109,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: Chave de máquina não encontrada
|
||||
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:
|
||||
NotExisting: Segredo não existe
|
||||
Invalid: Segredo é inválido
|
||||
|
@ -110,6 +110,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: Машинный ключ не найден
|
||||
AlreadyExisting: Машинный ключ уже существует
|
||||
Invalid: Открытый ключ не является допустимым открытым ключом RSA в формате PKIX с кодировкой PEM
|
||||
Secret:
|
||||
NotExisting: Ключ не существует
|
||||
Invalid: Ключ недействителен
|
||||
|
@ -109,6 +109,7 @@ Errors:
|
||||
Key:
|
||||
NotFound: 未找到机器密钥
|
||||
AlreadyExisting: 已有的机器钥匙
|
||||
Invalid: 公钥不是采用 PEM 编码的 PKIX 格式的有效 RSA 公钥
|
||||
Secret:
|
||||
NotExisting: 秘密并不存在
|
||||
Invalid: 秘密是无效的
|
||||
|
33
internal/v2/database/filter.go
Normal file
33
internal/v2/database/filter.go
Normal 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
|
||||
}
|
57
internal/v2/database/list_filter.go
Normal file
57
internal/v2/database/list_filter.go
Normal 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
|
||||
)
|
122
internal/v2/database/list_filter_test.go
Normal file
122
internal/v2/database/list_filter_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
139
internal/v2/database/mock/sql_mock.go
Normal file
139
internal/v2/database/mock/sql_mock.go
Normal 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()
|
||||
}
|
78
internal/v2/database/mock/type_converter.go
Normal file
78
internal/v2/database/mock/type_converter.go
Normal 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()
|
||||
}
|
100
internal/v2/database/number_filter.go
Normal file
100
internal/v2/database/number_filter.go
Normal 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
Loading…
x
Reference in New Issue
Block a user