Merge branch 'next' into next-rc

This commit is contained in:
Livio Spring 2024-09-26 07:00:27 +02:00
commit a5f7dc8c30
No known key found for this signature in database
GPG Key ID: 26BB1C2FA5952CF0
98 changed files with 1534 additions and 617 deletions

View File

@ -80,6 +80,11 @@ See all guides [here](https://zitadel.com/docs/self-hosting/deploy/overview)
### Setup ZITADEL Cloud (SaaS) ### Setup ZITADEL Cloud (SaaS)
If you want to experience a hands-free ZITADEL, you should use [ZITADEL Cloud](https://zitadel.com). If you want to experience a hands-free ZITADEL, you should use [ZITADEL Cloud](https://zitadel.com).
Available data regions are:
* 🇺🇸 United States
* 🇪🇺 European Union
* 🇦🇺 Australia
* 🇨🇭 Switzerland
ZITADEL Cloud comes with a free tier, providing you with all the same features as the open-source version. ZITADEL Cloud comes with a free tier, providing you with all the same features as the open-source version.
Learn more about the [pay-as-you-go pricing](https://zitadel.com/pricing). Learn more about the [pay-as-you-go pricing](https://zitadel.com/pricing).
@ -149,11 +154,12 @@ Deployment
- [Zero Downtime Updates](https://zitadel.com/docs/concepts/architecture/solution#zero-downtime-updates) - [Zero Downtime Updates](https://zitadel.com/docs/concepts/architecture/solution#zero-downtime-updates)
- [High scalability](https://zitadel.com/docs/self-hosting/manage/production) - [High scalability](https://zitadel.com/docs/self-hosting/manage/production)
Track upcoming features on our [roadmap](https://zitadel.com/roadmap). Track upcoming features on our [roadmap](https://zitadel.com/roadmap) and follow our [changelog](https://zitadel.com/changelog) for recent updates.
## How To Contribute ## How To Contribute
Find details about how you can contribute in our [Contribution Guide](./CONTRIBUTING.md) Find details about how you can contribute in our [Contribution Guide](./CONTRIBUTING.md).
Join our [Discord Chat](https://zitadel.com/chat) to get help.
## Contributors ## Contributors

View File

@ -3,8 +3,6 @@ package mirror
import ( import (
"context" "context"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/v2/eventstore" "github.com/zitadel/zitadel/internal/v2/eventstore"
"github.com/zitadel/zitadel/internal/v2/projection" "github.com/zitadel/zitadel/internal/v2/projection"
"github.com/zitadel/zitadel/internal/v2/readmodel" "github.com/zitadel/zitadel/internal/v2/readmodel"
@ -32,12 +30,12 @@ func queryLastSuccessfulMigration(ctx context.Context, destinationES *eventstore
return lastSuccess, nil return lastSuccess, nil
} }
func writeMigrationStart(ctx context.Context, sourceES *eventstore.EventStore, id string, destination string) (_ decimal.Decimal, err error) { func writeMigrationStart(ctx context.Context, sourceES *eventstore.EventStore, id string, destination string) (_ float64, err error) {
var cmd *eventstore.Command var cmd *eventstore.Command
if len(instanceIDs) > 0 { if len(instanceIDs) > 0 {
cmd, err = mirror_event.NewStartedInstancesCommand(destination, instanceIDs) cmd, err = mirror_event.NewStartedInstancesCommand(destination, instanceIDs)
if err != nil { if err != nil {
return decimal.Decimal{}, err return 0, err
} }
} else { } else {
cmd = mirror_event.NewStartedSystemCommand(destination) cmd = mirror_event.NewStartedSystemCommand(destination)
@ -60,12 +58,12 @@ func writeMigrationStart(ctx context.Context, sourceES *eventstore.EventStore, i
), ),
) )
if err != nil { if err != nil {
return decimal.Decimal{}, err return 0, err
} }
return position.Position, nil return position.Position, nil
} }
func writeMigrationSucceeded(ctx context.Context, destinationES *eventstore.EventStore, id, source string, position decimal.Decimal) error { func writeMigrationSucceeded(ctx context.Context, destinationES *eventstore.EventStore, id, source string, position float64) error {
return destinationES.Push( return destinationES.Push(
ctx, ctx,
eventstore.NewPushIntent( eventstore.NewPushIntent(

View File

@ -9,7 +9,6 @@ import (
"time" "time"
"github.com/jackc/pgx/v5/stdlib" "github.com/jackc/pgx/v5/stdlib"
"github.com/shopspring/decimal"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/zitadel/logging" "github.com/zitadel/logging"
@ -181,7 +180,7 @@ func copyEvents(ctx context.Context, source, dest *db.DB, bulkSize uint32) {
logging.WithFields("took", time.Since(start), "count", eventCount).Info("events migrated") logging.WithFields("took", time.Since(start), "count", eventCount).Info("events migrated")
} }
func writeCopyEventsDone(ctx context.Context, es *eventstore.EventStore, id, source string, position decimal.Decimal, errs <-chan error) { func writeCopyEventsDone(ctx context.Context, es *eventstore.EventStore, id, source string, position float64, errs <-chan error) {
joinedErrs := make([]error, 0, len(errs)) joinedErrs := make([]error, 0, len(errs))
for err := range errs { for err := range errs {
joinedErrs = append(joinedErrs, err) joinedErrs = append(joinedErrs, err)

View File

@ -24,7 +24,7 @@
<th class="availability" mat-header-cell *matHeaderCellDef> <th class="availability" mat-header-cell *matHeaderCellDef>
<span>{{ 'SMTP.LIST.ACTIVATED' | translate }}</span> <span>{{ 'SMTP.LIST.ACTIVATED' | translate }}</span>
</th> </th>
<td class="pointer availability" mat-cell *matCellDef="let config"> <td class="availability" [ngClass]="{ pointer: config.senderAddress }" mat-cell *matCellDef="let config">
<i <i
matTooltip="{{ 'SMTP.LIST.ACTIVATED' | translate }}" matTooltip="{{ 'SMTP.LIST.ACTIVATED' | translate }}"
*ngIf="isActive(config.state)" *ngIf="isActive(config.state)"
@ -36,7 +36,7 @@
<ng-container matColumnDef="description"> <ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef>{{ 'SETTING.SMTP.DESCRIPTION' | translate }}</th> <th mat-header-cell *matHeaderCellDef>{{ 'SETTING.SMTP.DESCRIPTION' | translate }}</th>
<td class="pointer" mat-cell *matCellDef="let config"> <td [ngClass]="{ pointer: config.senderAddress }" mat-cell *matCellDef="let config">
<span>{{ config?.description }}</span> <span>{{ config?.description }}</span>
</td> </td>
</ng-container> </ng-container>
@ -45,22 +45,22 @@
<th class="availability" mat-header-cell *matHeaderCellDef> <th class="availability" mat-header-cell *matHeaderCellDef>
<span>TLS</span> <span>TLS</span>
</th> </th>
<td class="pointer availability" mat-cell *matCellDef="let config"> <td class="availability" [ngClass]="{ pointer: config.senderAddress }" mat-cell *matCellDef="let config">
<i *ngIf="config.tls" class="las la-lock"></i> <i *ngIf="config.tls" class="las la-lock"></i>
<i *ngIf="!config.tls" class="las la-unlock"></i> <i *ngIf="config.senderAddress && !config.tls" class="las la-unlock"></i>
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="host"> <ng-container matColumnDef="host">
<th mat-header-cell *matHeaderCellDef>{{ 'SETTING.SMTP.HOSTANDPORT' | translate }}</th> <th mat-header-cell *matHeaderCellDef>{{ 'SETTING.SMTP.HOSTANDPORT' | translate }}</th>
<td class="pointer" mat-cell *matCellDef="let config"> <td [ngClass]="{ pointer: config.senderAddress }" mat-cell *matCellDef="let config">
<span>{{ config?.host }}</span> <span>{{ config?.host }}</span>
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="senderAddress"> <ng-container matColumnDef="senderAddress">
<th mat-header-cell *matHeaderCellDef>{{ 'SETTING.SMTP.SENDERADDRESS' | translate }}</th> <th mat-header-cell *matHeaderCellDef>{{ 'SETTING.SMTP.SENDERADDRESS' | translate }}</th>
<td class="pointer" mat-cell *matCellDef="let config"> <td [ngClass]="{ pointer: config.senderAddress }" mat-cell *matCellDef="let config">
<span>{{ config?.senderAddress }}</span> <span>{{ config?.senderAddress }}</span>
</td> </td>
</ng-container> </ng-container>
@ -95,7 +95,7 @@
<button <button
actions actions
[disabled]="(['iam.write'] | hasRole | async) === false" [disabled]="(['iam.write'] | hasRole | async) === false || !config?.senderAddress"
mat-icon-button mat-icon-button
color="primary" color="primary"
matTooltip="{{ 'SMTP.LIST.TEST' | translate }}" matTooltip="{{ 'SMTP.LIST.TEST' | translate }}"

View File

@ -205,6 +205,9 @@ export class SMTPTableComponent implements OnInit {
} }
public navigateToProvider(row: SMTPConfig.AsObject) { public navigateToProvider(row: SMTPConfig.AsObject) {
if (!row.senderAddress) {
return;
}
this.router.navigate(this.routerLinkForRow(row)); this.router.navigate(this.routerLinkForRow(row));
} }
} }

View File

@ -154,10 +154,25 @@
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef>{{ 'PROJECT.GRANT.STATE' | translate }}</th>
<td mat-cell *matCellDef="let grant">
<span
class="state"
[ngClass]="{
active: grant.state === UserGrantState.USER_GRANT_STATE_ACTIVE,
inactive: grant.state === UserGrantState.USER_GRANT_STATE_INACTIVE,
}"
>
{{ 'USER.DATA.STATE' + grant.state | translate }}
</span>
</td>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd> <ng-container matColumnDef="actions" stickyEnd>
<th mat-header-cell *matHeaderCellDef class="user-tr-actions"></th> <th mat-header-cell *matHeaderCellDef class="user-tr-actions"></th>
<td mat-cell class="user-tr-actions" *matCellDef="let grant; let i = index"> <td mat-cell class="user-tr-actions" *matCellDef="let grant; let i = index">
<cnsl-table-actions [hasActions]="true"> <cnsl-table-actions [hasActions]="!context.includes('user')">
<button <button
actions actions
matTooltip="{{ 'ACTIONS.REMOVE' | translate }}" matTooltip="{{ 'ACTIONS.REMOVE' | translate }}"

View File

@ -8,7 +8,13 @@ import { tap } from 'rxjs/operators';
import { enterAnimations } from 'src/app/animations'; import { enterAnimations } from 'src/app/animations';
import { UserGrant as AuthUserGrant } from 'src/app/proto/generated/zitadel/auth_pb'; import { UserGrant as AuthUserGrant } from 'src/app/proto/generated/zitadel/auth_pb';
import { Role } from 'src/app/proto/generated/zitadel/project_pb'; import { Role } from 'src/app/proto/generated/zitadel/project_pb';
import { Type, UserGrant as MgmtUserGrant, UserGrantQuery, UserGrant } from 'src/app/proto/generated/zitadel/user_pb'; import {
Type,
UserGrant as MgmtUserGrant,
UserGrant,
UserGrantQuery,
UserGrantState,
} from 'src/app/proto/generated/zitadel/user_pb';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ManagementService } from 'src/app/services/mgmt.service'; import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
@ -66,6 +72,7 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
public UserGrantContext: any = UserGrantContext; public UserGrantContext: any = UserGrantContext;
public Type: any = Type; public Type: any = Type;
public ActionKeysType: any = ActionKeysType; public ActionKeysType: any = ActionKeysType;
public UserGrantState: any = UserGrantState;
@Input() public type: Type | undefined = undefined; @Input() public type: Type | undefined = undefined;
public filterOpen: boolean = false; public filterOpen: boolean = false;
@ -86,6 +93,7 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
'type', 'type',
'creationDate', 'creationDate',
'changeDate', 'changeDate',
'state',
'roleNamesList', 'roleNamesList',
'actions', 'actions',
]; ];

View File

@ -17,6 +17,7 @@
'type', 'type',
'creationDate', 'creationDate',
'changeDate', 'changeDate',
'state',
'roleNamesList', 'roleNamesList',
'actions', 'actions',
]" ]"

View File

@ -42,7 +42,16 @@
[context]="UserGrantContext.GRANTED_PROJECT" [context]="UserGrantContext.GRANTED_PROJECT"
[projectId]="projectId" [projectId]="projectId"
[grantId]="grantId" [grantId]="grantId"
[displayedColumns]="['select', 'user', 'projectId', 'creationDate', 'changeDate', 'roleNamesList', 'actions']" [displayedColumns]="[
'select',
'user',
'projectId',
'creationDate',
'changeDate',
'state',
'roleNamesList',
'actions',
]"
[disableWrite]="(['user.grant.write$', 'user.grant.write:' + grantId] | hasRole | async) === false" [disableWrite]="(['user.grant.write$', 'user.grant.write:' + grantId] | hasRole | async) === false"
[disableDelete]="(['user.grant.delete$', 'user.grant.delete:' + grantId] | hasRole | async) === false" [disableDelete]="(['user.grant.delete$', 'user.grant.delete:' + grantId] | hasRole | async) === false"
[refreshOnPreviousRoutes]="['/grant-create/project/{{projectId}}/grant/{{grantId}}']" [refreshOnPreviousRoutes]="['/grant-create/project/{{projectId}}/grant/{{grantId}}']"

View File

@ -187,7 +187,7 @@
<cnsl-user-grants <cnsl-user-grants
[context]="UserGrantContext.OWNED_PROJECT" [context]="UserGrantContext.OWNED_PROJECT"
[projectId]="projectId" [projectId]="projectId"
[displayedColumns]="['select', 'user', 'creationDate', 'changeDate', 'roleNamesList', 'actions']" [displayedColumns]="['select', 'user', 'creationDate', 'changeDate', 'state', 'roleNamesList', 'actions']"
[refreshOnPreviousRoutes]="['/grant-create/project/' + projectId]" [refreshOnPreviousRoutes]="['/grant-create/project/' + projectId]"
[disableWrite]="(['user.grant.write$', 'user.grant.write:' + projectId] | hasRole | async) === false" [disableWrite]="(['user.grant.write$', 'user.grant.write:' + projectId] | hasRole | async) === false"
[disableDelete]="(['user.grant.delete$', 'user.grant.delete:' + projectId] | hasRole | async) === false" [disableDelete]="(['user.grant.delete$', 'user.grant.delete:' + projectId] | hasRole | async) === false"

View File

@ -136,7 +136,16 @@
<cnsl-user-grants <cnsl-user-grants
[userId]="user.id" [userId]="user.id"
[context]="USERGRANTCONTEXT" [context]="USERGRANTCONTEXT"
[displayedColumns]="['org', 'projectId', 'type', 'creationDate', 'changeDate', 'roleNamesList', 'actions']" [displayedColumns]="[
'org',
'projectId',
'type',
'creationDate',
'changeDate',
'state',
'roleNamesList',
'actions',
]"
[disableWrite]="(['user.grant.write$'] | hasRole | async) === false" [disableWrite]="(['user.grant.write$'] | hasRole | async) === false"
[disableDelete]="(['user.grant.delete$'] | hasRole | async) === false" [disableDelete]="(['user.grant.delete$'] | hasRole | async) === false"
> >

View File

@ -222,7 +222,7 @@
<cnsl-user-grants <cnsl-user-grants
[userId]="user.id" [userId]="user.id"
[context]="USERGRANTCONTEXT" [context]="USERGRANTCONTEXT"
[displayedColumns]="['select', 'projectId', 'creationDate', 'changeDate', 'roleNamesList', 'actions']" [displayedColumns]="['select', 'projectId', 'creationDate', 'changeDate', 'state', 'roleNamesList', 'actions']"
[disableWrite]="(['user.grant.write$'] | hasRole | async) === false" [disableWrite]="(['user.grant.write$'] | hasRole | async) === false"
[disableDelete]="(['user.grant.delete$'] | hasRole | async) === false" [disableDelete]="(['user.grant.delete$'] | hasRole | async) === false"
> >

View File

@ -12,8 +12,6 @@ The typescript repository contains all TypeScript and JavaScript packages and ap
- `@zitadel/proto`: Typescript implementation of Protocol Buffers, suitable for web browsers and Node.js. - `@zitadel/proto`: Typescript implementation of Protocol Buffers, suitable for web browsers and Node.js.
- `@zitadel/client`: Core components for establishing a client connection - `@zitadel/client`: Core components for establishing a client connection
- `@zitadel/node`: Core components for establishing a server connection - `@zitadel/node`: Core components for establishing a server connection
- `@zitadel/react`: Shared React Utilities and components built with Tailwind CSS
- `@zitadel/next`: Shared Next.js Utilities
- `@zitadel/tsconfig`: shared `tsconfig.json`s used throughout the monorepo - `@zitadel/tsconfig`: shared `tsconfig.json`s used throughout the monorepo
- `eslint-config-zitadel`: ESLint preset - `eslint-config-zitadel`: ESLint preset
@ -47,10 +45,13 @@ It does so by redirecting to `/login`.
The login is then able to load an [AuthRequest](/docs/apis/resources/oidc_service_v2/oidc-service-get-auth-request#get-oidc-auth-request-details). The login is then able to load an [AuthRequest](/docs/apis/resources/oidc_service_v2/oidc-service-get-auth-request#get-oidc-auth-request-details).
The Auth Request defines how users proceed to authenticate. If no special prompts or scopes are set, the login brings up the `/loginname` page. The Auth Request defines how users proceed to authenticate. If no special prompts or scopes are set, the login brings up the `/loginname` page.
The /loginname page allows to enter loginname or email of a user. User discovery is implemented at /api/loginname and if the user is found, they will be redirected to the available authentication method page. The /loginname page allows to enter loginname or email of a user which is then used to search for a user.
Right after the user is found, a session is created and set as cookie. If the user is found, a session is created and set as cookie, then the user is redirected to the available authentication method page.
This cookie is then hydrated with more information once the users continues. While the users continues and provides more information, the cookie is hydrated with this information until a final state is reached.
The communication from the browser to the server is done by [NextJS Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
The OIDC Auth request is always passed in the url to have a context to the ongoing authentication flow. The OIDC Auth request is always passed in the url to have a context to the ongoing authentication flow.
If enough user information is retrieved and the user is authenticated according to the policies, the flow is finalized by [requesting a the callback url](/docs/apis/resources/oidc_service_v2/oidc-service-create-callback) for the auth request and the user is redirected back to the application. If enough user information is retrieved and the user is authenticated according to the policies, the flow is finalized by [requesting a the callback url](/docs/apis/resources/oidc_service_v2/oidc-service-create-callback) for the auth request and the user is redirected back to the application.
The application can then request a token calling the /token endpoint of the login which is proxied to the ZITADEL API. The application can then request a token calling the /token endpoint of the login which is proxied to the ZITADEL API.
@ -91,9 +92,9 @@ The application can then request a token calling the /token endpoint of the logi
- [x] Passkey - [x] Passkey
- [x] IDPs - [x] IDPs
- [x] Google - [x] Google
- [ ] GitHub - [x] GitHub
- [x] GitLab - [x] GitLab
- [ ] Azure - [x] Azure
- [ ] Apple - [ ] Apple
- Register - Register
- [x] Email Password - [x] Email Password
@ -113,7 +114,7 @@ Authenticated users are directly able to self service their account.
- [x] OTP via email - [x] OTP via email
- [x] OTP via SMS - [x] OTP via SMS
- [x] Setup Multifactor Passkey (U2F) - [x] Setup Multifactor Passkey (U2F)
- [ ] Validate Account (email verification) - [x] Validate Account (email verification)
### Setup ### Setup
@ -122,10 +123,18 @@ In order to run the new login app, make sure to follow the instructions in the [
#### How to setup domains for OIDC #### How to setup domains for OIDC
When setting up the new login app for OIDC, the domain must be registered on your instance and use https. When setting up the new login app for OIDC, the domain must be registered on your instance and use https.
If you are using a self hosted instance, [install](/docs/apis/resources/system/system-service-add-domain) your domain on your instance using the system service. To register your login domain on your instance, [add](/docs/apis/resources/admin/admin-service-add-instance-trusted-domain) your domain on your instances trusted domains.
If you want to use the login with your cloud instance, you can purchase your domain on zitadel.com, install it on your domain following [our guide](/docs/guides/manage/cloud/instances#add-custom-domain). #### OIDC Proxy
After your domain has been verified, you can reconfigure your DNS settings in order to deploy the login on your own.
When setting up the new login app for OIDC, ensure it meets the following requirements:
- The OIDC Proxy is deployed and running on HTTPS
- The OIDC Proxy sets `x-zitadel-login-client` which is the user ID of the service account
- The OIDC Proxy sets `x-zitadel-public-host` which is the host, your login is deployed to `ex. login.example.com`.
- The OIDC Proxy sets `x-zitadel-instance-host` which is the host of your instance `ex. test-hdujwl.zitadel.cloud`.
You can review an example implementation of a middlware [here](https://github.com/zitadel/typescript/blob/main/apps/login/src/middleware.ts).
#### Deploy to Vercel #### Deploy to Vercel
@ -135,5 +144,6 @@ To deploy your own version on Vercel, navigate to your instance and create a ser
Copy its id from the overview and set it as `ZITADEL_SERVICE_USER_ID`. Copy its id from the overview and set it as `ZITADEL_SERVICE_USER_ID`.
Then create a personal access token (PAT), copy and set it as `ZITADEL_SERVICE_USER_TOKEN`, then navigate to Default settings and make sure it gets `IAM_OWNER` permissions. Then create a personal access token (PAT), copy and set it as `ZITADEL_SERVICE_USER_TOKEN`, then navigate to Default settings and make sure it gets `IAM_OWNER` permissions.
Finally set your instance url as `ZITADEL_API_URL`. Make sure to set it without trailing slash. Finally set your instance url as `ZITADEL_API_URL`. Make sure to set it without trailing slash.
Also ensure your login domain is registered on your instance by adding it as a [trusted domain](/docs/apis/resources/admin/admin-service-add-instance-trusted-domain).
![Deploy to Vercel](/img/deploy-to-vercel.png) ![Deploy to Vercel](/img/deploy-to-vercel.png)

View File

@ -0,0 +1,74 @@
---
title: SMS, SMTP and HTTP Provider for Notifications
---
All Notifications send as SMS and Email are customizable as that you can define your own providers,
which then send the notifications out. These providers can also be defined as an HTTP type,
and the text and content, which is used to send the SMS's and Emails will get send to a Webhook as JSON.
With this everything can be customized or even custom logic can be implemented to use a not yet supported provider by ZITADEL.
## How it works
There is a default provider configured in ZITADEL Cloud, both for SMS's and Emails, but this default providers can be changed through the respective API's.
This API's are provided on an instance level:
- [SMS Providers](/apis/resources/admin/sms-provider)
- [Email Providers](/apis/resources/admin/email-provider)
To use a non-default provider just add, and then activate. There can only be 1 provider be activated at the same time.
## Resulting messages
In case of the Twilio and SMTP providers, the messages will be sent as before, in case of the HTTP providers the content of the messages is the same but as a HTTP call.
Here an example of the body of an Email sent via HTTP provider:
```json
{
"contextInfo": {
"eventType": "user.human.initialization.code.added",
"provider": {
"id": "285181292935381355",
"description": "test"
},
"recipientEmailAddress": "example@zitadel.com"
},
"templateData": {
"title": "Zitadel - Initialize User",
"preHeader": "Initialize User",
"subject": "Initialize User",
"greeting": "Hello GivenName FamilyName,",
"text": "This user was created in Zitadel. Use the username Username to login. Please click the button below to finish the initialization process. (Code 0M53RF) If you didn't ask for this mail, please ignore it.",
"url": "http://example.zitadel.com/ui/login/user/init?authRequestID=\u0026code=0M53RF\u0026loginname=Username\u0026orgID=275353657317327214\u0026passwordset=false\u0026userID=285181014567813483",
"buttonText": "Finish initialization",
"primaryColor": "#5469d4",
"backgroundColor": "#fafafa",
"fontColor": "#000000",
"fontFamily": "-apple-system, BlinkMacSystemFont, Segoe UI, Lato, Arial, Helvetica, sans-serif",
"footerText": "InitCode.Footer"
},
"args": {
"changeDate": "2024-09-16T10:58:50.73237+02:00",
"code": "0M53RF",
"creationDate": "2024-09-16T10:58:50.73237+02:00",
"displayName": "GivenName FamilyName",
"firstName": "GivenName",
"lastEmail": "example@zitadel.com",
"lastName": "FamilyName",
"lastPhone": "+41791234567",
"loginNames": [
"Username"
],
"nickName": "",
"preferredLoginName": "Username",
"userName": "Username",
"verifiedEmail": "example@zitadel.com",
"verifiedPhone": ""
}
}
```
There are 3 elements to this message:
- contextInfo, with information on why this message is sent like the Event, which Email or SMS provider is used and which recipient should receive this message
- templateData, with all texts and format information which can be used with a template to produce the desired message
- args, with the information provided to the user which can be used in the message to customize

View File

@ -0,0 +1,37 @@
If you operate Zitadel behind a Reverse Proxy or Ingress inside a Kubernetes cluster,
you may encounter an Error like `upstream sent too big header while reading response header from upstream`
in your NGINX Logs and receive a 403 Error when accessing NGINX.
you can solve it by increasing the grpc buffer size in your nginx config:
### Ingress NGINX
```yaml
ingress:
enabled: true
annotations:
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleRemoveById 949110
nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
nginx.ingress.kubernetes.io/configuration-snippet: |
grpc_set_header Host $host;
more_clear_input_headers "Host" "X-Forwarded-Host";
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_x_forwarded_host;
# highlight-next-line
nginx.ingress.kubernetes.io/server-snippet: "grpc_buffer_size 8k;"
```
### NGINX Config
```nginx
http {
server {
listen 80;
http2 on;
location / {
grpc_pass grpc://zitadel-disabled-tls:8080;
grpc_set_header Host $host:$server_port;
# highlight-next-line
grpc_buffer_size 8k;
}
}
}
```

View File

@ -3,8 +3,14 @@ title: Troubleshoot ZITADEL
--- ---
import InstanceNotFound from '/docs/self-hosting/deploy/troubleshooting/_instance_not_found.mdx'; import InstanceNotFound from '/docs/self-hosting/deploy/troubleshooting/_instance_not_found.mdx';
import UpstreamHeader from '/docs/self-hosting/deploy/troubleshooting/_upstream_header.mdx'
## Instance not found ## Instance not found
<InstanceNotFound/> <InstanceNotFound/>
## upstream sent too big header while reading response header from upstream
<UpstreamHeader/>

View File

@ -158,6 +158,7 @@ module.exports = {
"guides/manage/customize/texts", "guides/manage/customize/texts",
"guides/manage/customize/behavior", "guides/manage/customize/behavior",
"guides/manage/customize/restrictions", "guides/manage/customize/restrictions",
"guides/manage/customize/notifications",
], ],
}, },
{ {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 69 KiB

2
go.mod
View File

@ -38,7 +38,6 @@ require (
github.com/h2non/gock v1.2.0 github.com/h2non/gock v1.2.0
github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/improbable-eng/grpc-web v0.15.0 github.com/improbable-eng/grpc-web v0.15.0
github.com/jackc/pgx-shopspring-decimal v0.0.0-20220624020537-1d36b5a1853e
github.com/jackc/pgx/v5 v5.6.0 github.com/jackc/pgx/v5 v5.6.0
github.com/jarcoal/jpath v0.0.0-20140328210829-f76b8b2dbf52 github.com/jarcoal/jpath v0.0.0-20140328210829-f76b8b2dbf52
github.com/jinzhu/gorm v1.9.16 github.com/jinzhu/gorm v1.9.16
@ -55,7 +54,6 @@ require (
github.com/rakyll/statik v0.1.7 github.com/rakyll/statik v0.1.7
github.com/rs/cors v1.11.0 github.com/rs/cors v1.11.0
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
github.com/shopspring/decimal v1.4.0
github.com/sony/sonyflake v1.2.0 github.com/sony/sonyflake v1.2.0
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0 github.com/spf13/viper v1.19.0

4
go.sum
View File

@ -405,8 +405,6 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx-shopspring-decimal v0.0.0-20220624020537-1d36b5a1853e h1:i3gQ/Zo7sk4LUVbsAjTNeC4gIjoPNIZVzs4EXstssV4=
github.com/jackc/pgx-shopspring-decimal v0.0.0-20220624020537-1d36b5a1853e/go.mod h1:zUHglCZ4mpDUPgIwqEKoba6+tcUQzRdb1+DPTuYe9pI=
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
@ -651,8 +649,6 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=

View File

@ -76,7 +76,7 @@ func (s *SystemTokenVerifierFromConfig) VerifySystemToken(ctx context.Context, t
type systemJWTStorage struct { type systemJWTStorage struct {
keys map[string]*SystemAPIUser keys map[string]*SystemAPIUser
mutex sync.Mutex mutex sync.RWMutex
cachedKeys map[string]*rsa.PublicKey cachedKeys map[string]*rsa.PublicKey
} }
@ -98,7 +98,9 @@ func (s *SystemAPIUser) readKey() (*rsa.PublicKey, error) {
} }
func (s *systemJWTStorage) GetKeyByIDAndClientID(_ context.Context, _, userID string) (*jose.JSONWebKey, error) { func (s *systemJWTStorage) GetKeyByIDAndClientID(_ context.Context, _, userID string) (*jose.JSONWebKey, error) {
s.mutex.RLock()
cachedKey, ok := s.cachedKeys[userID] cachedKey, ok := s.cachedKeys[userID]
s.mutex.RUnlock()
if ok { if ok {
return &jose.JSONWebKey{KeyID: userID, Key: cachedKey}, nil return &jose.JSONWebKey{KeyID: userID, Key: cachedKey}, nil
} }

View File

@ -20,17 +20,20 @@ func listSMTPConfigsToModel(req *admin_pb.ListSMTPConfigsRequest) (*query.SMTPCo
} }
func SMTPConfigToProviderPb(config *query.SMTPConfig) *settings_pb.SMTPConfig { func SMTPConfigToProviderPb(config *query.SMTPConfig) *settings_pb.SMTPConfig {
return &settings_pb.SMTPConfig{ ret := &settings_pb.SMTPConfig{
Details: object.ToViewDetailsPb(config.Sequence, config.CreationDate, config.ChangeDate, config.ResourceOwner), Details: object.ToViewDetailsPb(config.Sequence, config.CreationDate, config.ChangeDate, config.ResourceOwner),
Id: config.ID, Id: config.ID,
Description: config.Description, Description: config.Description,
Tls: config.SMTPConfig.TLS, State: SMTPConfigStateToPb(config.State),
Host: config.SMTPConfig.Host,
User: config.SMTPConfig.User,
State: SMTPConfigStateToPb(config.State),
SenderAddress: config.SMTPConfig.SenderAddress,
SenderName: config.SMTPConfig.SenderName,
} }
if config.SMTPConfig != nil {
ret.Tls = config.SMTPConfig.TLS
ret.Host = config.SMTPConfig.Host
ret.User = config.SMTPConfig.User
ret.SenderAddress = config.SMTPConfig.SenderAddress
ret.SenderName = config.SMTPConfig.SenderName
}
return ret
} }
func SMTPConfigsToPb(configs []*query.SMTPConfig) []*settings_pb.SMTPConfig { func SMTPConfigsToPb(configs []*query.SMTPConfig) []*settings_pb.SMTPConfig {

View File

@ -55,5 +55,6 @@ func UserGrantToPb(grant *query.UserGrant) *auth_pb.UserGrant {
ProjectGrantId: grant.GrantID, ProjectGrantId: grant.GrantID,
RoleKeys: grant.Roles, RoleKeys: grant.Roles,
UserType: user.TypeToPb(grant.UserType), UserType: user.TypeToPb(grant.UserType),
State: user.UserGrantStateToPb(grant.State),
} }
} }

View File

@ -23,7 +23,7 @@ func UserGrantToPb(assetPrefix string, grant *query.UserGrant) *user_pb.UserGran
return &user_pb.UserGrant{ return &user_pb.UserGrant{
Id: grant.ID, Id: grant.ID,
UserId: grant.UserID, UserId: grant.UserID,
State: user_pb.UserGrantState_USER_GRANT_STATE_ACTIVE, State: UserGrantStateToPb(grant.State),
RoleKeys: grant.Roles, RoleKeys: grant.Roles,
ProjectId: grant.ProjectID, ProjectId: grant.ProjectID,
OrgId: grant.ResourceOwner, OrgId: grant.ResourceOwner,
@ -51,6 +51,21 @@ func UserGrantToPb(assetPrefix string, grant *query.UserGrant) *user_pb.UserGran
} }
} }
func UserGrantStateToPb(state domain.UserGrantState) user_pb.UserGrantState {
switch state {
case domain.UserGrantStateActive:
return user_pb.UserGrantState_USER_GRANT_STATE_ACTIVE
case domain.UserGrantStateInactive:
return user_pb.UserGrantState_USER_GRANT_STATE_INACTIVE
case domain.UserGrantStateRemoved,
domain.UserGrantStateUnspecified:
// these states should never occur here and are mainly listed for linting purposes
fallthrough
default:
return user_pb.UserGrantState_USER_GRANT_STATE_UNSPECIFIED
}
}
func UserGrantQueriesToQuery(ctx context.Context, queries []*user_pb.UserGrantQuery) (q []query.SearchQuery, err error) { func UserGrantQueriesToQuery(ctx context.Context, queries []*user_pb.UserGrantQuery) (q []query.SearchQuery, err error) {
q = make([]query.SearchQuery, len(queries)) q = make([]query.SearchQuery, len(queries))
for i, query := range queries { for i, query := range queries {

View File

@ -50,13 +50,10 @@ func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (_ op.Cl
err = oidcError(err) err = oidcError(err)
span.EndWithError(err) span.EndWithError(err)
}() }()
client, err := o.query.GetOIDCClientByID(ctx, id, false) client, err := o.query.ActiveOIDCClientByID(ctx, id, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if client.State != domain.AppStateActive {
return nil, zerrors.ThrowPreconditionFailed(nil, "OIDC-sdaGg", "client is not active")
}
return ClientFromBusiness(client, o.defaultLoginURL, o.defaultLoginURLV2), nil return ClientFromBusiness(client, o.defaultLoginURL, o.defaultLoginURLV2), nil
} }
@ -333,6 +330,9 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo *oidc.UserInfo, us
if err != nil { if err != nil {
return err return err
} }
if user.State != domain.UserStateActive {
return zerrors.ThrowUnauthenticated(nil, "OIDC-S3tha", "Errors.Users.NotActive")
}
var allRoles bool var allRoles bool
roles := make([]string, 0) roles := make([]string, 0)
for _, scope := range scopes { for _, scope := range scopes {
@ -802,19 +802,24 @@ func (o *OPStorage) assertRoles(ctx context.Context, userID, applicationID strin
if projectID != "" { if projectID != "" {
roleAudience = append(roleAudience, projectID) roleAudience = append(roleAudience, projectID)
} }
queries := make([]query.SearchQuery, 0, 2)
projectQuery, err := query.NewUserGrantProjectIDsSearchQuery(roleAudience) projectQuery, err := query.NewUserGrantProjectIDsSearchQuery(roleAudience)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
queries = append(queries, projectQuery)
userIDQuery, err := query.NewUserGrantUserIDSearchQuery(userID) userIDQuery, err := query.NewUserGrantUserIDSearchQuery(userID)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
queries = append(queries, userIDQuery) activeQuery, err := query.NewUserGrantStateQuery(domain.UserGrantStateActive)
if err != nil {
return nil, nil, err
}
grants, err := o.query.UserGrants(ctx, &query.UserGrantsQueries{ grants, err := o.query.UserGrants(ctx, &query.UserGrantsQueries{
Queries: queries, Queries: []query.SearchQuery{
projectQuery,
userIDQuery,
activeQuery,
},
}, true) }, true)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -979,16 +984,13 @@ func (s *Server) VerifyClient(ctx context.Context, r *op.Request[op.ClientCreden
if err != nil { if err != nil {
return nil, err return nil, err
} }
client, err := s.query.GetOIDCClientByID(ctx, clientID, assertion) client, err := s.query.ActiveOIDCClientByID(ctx, clientID, assertion)
if zerrors.IsNotFound(err) { if zerrors.IsNotFound(err) {
return nil, oidc.ErrInvalidClient().WithParent(err).WithReturnParentToClient(authz.GetFeatures(ctx).DebugOIDCParentError).WithDescription("client not found") return nil, oidc.ErrInvalidClient().WithParent(err).WithReturnParentToClient(authz.GetFeatures(ctx).DebugOIDCParentError).WithDescription("no active client not found")
} }
if err != nil { if err != nil {
return nil, err // defaults to server error return nil, err // defaults to server error
} }
if client.State != domain.AppStateActive {
return nil, oidc.ErrInvalidClient().WithDescription("client is not active")
}
if client.Settings == nil { if client.Settings == nil {
client.Settings = &query.OIDCSettings{ client.Settings = &query.OIDCSettings{
AccessTokenLifetime: s.defaultAccessTokenLifetime, AccessTokenLifetime: s.defaultAccessTokenLifetime,

View File

@ -196,9 +196,13 @@ func TestServer_VerifyClient(t *testing.T) {
sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId()) sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
project, err := Instance.CreateProject(CTX) project, err := Instance.CreateProject(CTX)
require.NoError(t, err) require.NoError(t, err)
projectInactive, err := Instance.CreateProject(CTX)
require.NoError(t, err)
inactiveClient, err := Instance.CreateOIDCInactivateClient(CTX, redirectURI, logoutRedirectURI, project.GetId()) inactiveClient, err := Instance.CreateOIDCInactivateClient(CTX, redirectURI, logoutRedirectURI, project.GetId())
require.NoError(t, err) require.NoError(t, err)
inactiveProjectClient, err := Instance.CreateOIDCInactivateProjectClient(CTX, redirectURI, logoutRedirectURI, projectInactive.GetId())
require.NoError(t, err)
nativeClient, err := Instance.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), false) nativeClient, err := Instance.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), false)
require.NoError(t, err) require.NoError(t, err)
basicWebClient, err := Instance.CreateOIDCWebClientBasic(CTX, redirectURI, logoutRedirectURI, project.GetId()) basicWebClient, err := Instance.CreateOIDCWebClientBasic(CTX, redirectURI, logoutRedirectURI, project.GetId())
@ -240,6 +244,14 @@ func TestServer_VerifyClient(t *testing.T) {
}, },
wantErr: true, wantErr: true,
}, },
{
name: "client inactive (project) error",
client: clientDetails{
authReqClientID: nativeClient.GetClientId(),
clientID: inactiveProjectClient.GetClientId(),
},
wantErr: true,
},
{ {
name: "native client success", name: "native client success",
client: clientDetails{ client: clientDetails{

View File

@ -24,6 +24,9 @@ func TestServer_ClientCredentialsExchange(t *testing.T) {
machine, name, clientID, clientSecret, err := Instance.CreateOIDCCredentialsClient(CTX) machine, name, clientID, clientSecret, err := Instance.CreateOIDCCredentialsClient(CTX)
require.NoError(t, err) require.NoError(t, err)
_, _, clientIDInactive, clientSecretInactive, err := Instance.CreateOIDCCredentialsClientInactive(CTX)
require.NoError(t, err)
type claims struct { type claims struct {
name string name string
username string username string
@ -71,6 +74,13 @@ func TestServer_ClientCredentialsExchange(t *testing.T) {
scope: []string{oidc.ScopeOpenID}, scope: []string{oidc.ScopeOpenID},
wantErr: true, wantErr: true,
}, },
{
name: "inactive machine user error",
clientID: clientIDInactive,
clientSecret: clientSecretInactive,
scope: []string{oidc.ScopeOpenID},
wantErr: true,
},
{ {
name: "wrong secret error", name: "wrong secret error",
clientID: clientID, clientID: clientID,

View File

@ -213,7 +213,7 @@ func (s *Server) clientFromCredentials(ctx context.Context, cc *op.ClientCredent
if err != nil { if err != nil {
return nil, err return nil, err
} }
client, err = s.query.GetIntrospectionClientByID(ctx, clientID, assertion) client, err = s.query.ActiveIntrospectionClientByID(ctx, clientID, assertion)
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return nil, oidc.ErrUnauthorizedClient().WithParent(err).WithReturnParentToClient(authz.GetFeatures(ctx).DebugOIDCParentError) return nil, oidc.ErrUnauthorizedClient().WithParent(err).WithReturnParentToClient(authz.GetFeatures(ctx).DebugOIDCParentError)
} }

View File

@ -11,7 +11,6 @@ import (
"github.com/go-jose/go-jose/v4" "github.com/go-jose/go-jose/v4"
"github.com/jonboulle/clockwork" "github.com/jonboulle/clockwork"
"github.com/muhlemmer/gu" "github.com/muhlemmer/gu"
"github.com/shopspring/decimal"
"github.com/zitadel/logging" "github.com/zitadel/logging"
"github.com/zitadel/oidc/v3/pkg/op" "github.com/zitadel/oidc/v3/pkg/op"
@ -351,14 +350,14 @@ func (o *OPStorage) getSigningKey(ctx context.Context) (op.SigningKey, error) {
if len(keys.Keys) > 0 { if len(keys.Keys) > 0 {
return o.privateKeyToSigningKey(selectSigningKey(keys.Keys)) return o.privateKeyToSigningKey(selectSigningKey(keys.Keys))
} }
var position decimal.Decimal var position float64
if keys.State != nil { if keys.State != nil {
position = keys.State.Position position = keys.State.Position
} }
return nil, o.refreshSigningKey(ctx, o.signingKeyAlgorithm, position) return nil, o.refreshSigningKey(ctx, o.signingKeyAlgorithm, position)
} }
func (o *OPStorage) refreshSigningKey(ctx context.Context, algorithm string, position decimal.Decimal) error { func (o *OPStorage) refreshSigningKey(ctx context.Context, algorithm string, position float64) error {
ok, err := o.ensureIsLatestKey(ctx, position) ok, err := o.ensureIsLatestKey(ctx, position)
if err != nil || !ok { if err != nil || !ok {
return zerrors.ThrowInternal(err, "OIDC-ASfh3", "cannot ensure that projection is up to date") return zerrors.ThrowInternal(err, "OIDC-ASfh3", "cannot ensure that projection is up to date")
@ -370,12 +369,12 @@ func (o *OPStorage) refreshSigningKey(ctx context.Context, algorithm string, pos
return zerrors.ThrowInternal(nil, "OIDC-Df1bh", "") return zerrors.ThrowInternal(nil, "OIDC-Df1bh", "")
} }
func (o *OPStorage) ensureIsLatestKey(ctx context.Context, position decimal.Decimal) (bool, error) { func (o *OPStorage) ensureIsLatestKey(ctx context.Context, position float64) (bool, error) {
maxSequence, err := o.getMaxKeySequence(ctx) maxSequence, err := o.getMaxKeySequence(ctx)
if err != nil { if err != nil {
return false, fmt.Errorf("error retrieving new events: %w", err) return false, fmt.Errorf("error retrieving new events: %w", err)
} }
return position.GreaterThanOrEqual(maxSequence), nil return position >= maxSequence, nil
} }
func (o *OPStorage) privateKeyToSigningKey(key query.PrivateKey) (_ op.SigningKey, err error) { func (o *OPStorage) privateKeyToSigningKey(key query.PrivateKey) (_ op.SigningKey, err error) {
@ -413,9 +412,9 @@ func (o *OPStorage) lockAndGenerateSigningKeyPair(ctx context.Context, algorithm
return o.command.GenerateSigningKeyPair(setOIDCCtx(ctx), algorithm) return o.command.GenerateSigningKeyPair(setOIDCCtx(ctx), algorithm)
} }
func (o *OPStorage) getMaxKeySequence(ctx context.Context) (decimal.Decimal, error) { func (o *OPStorage) getMaxKeySequence(ctx context.Context) (float64, error) {
return o.eventstore.LatestPosition(ctx, return o.eventstore.LatestSequence(ctx,
eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxPosition). eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxSequence).
ResourceOwner(authz.GetInstance(ctx).InstanceID()). ResourceOwner(authz.GetInstance(ctx).InstanceID()).
AwaitOpenTransactions(). AwaitOpenTransactions().
AllowTimeTravel(). AllowTimeTravel().

View File

@ -66,7 +66,10 @@ func (s *Server) UserInfo(ctx context.Context, r *op.Request[oidc.UserInfoReques
false, false,
)(ctx, true, domain.TriggerTypePreUserinfoCreation) )(ctx, true, domain.TriggerTypePreUserinfoCreation)
if err != nil { if err != nil {
return nil, err if !zerrors.IsNotFound(err) {
return nil, err
}
return nil, op.NewStatusError(oidc.ErrAccessDenied().WithDescription("no active user").WithParent(err).WithReturnParentToClient(authz.GetFeatures(ctx).DebugOIDCParentError), http.StatusUnauthorized)
} }
return op.NewResponse(userInfo), nil return op.NewResponse(userInfo), nil
} }
@ -163,11 +166,11 @@ func prepareRoles(ctx context.Context, scope []string, projectID string, project
} }
func userInfoToOIDC(user *query.OIDCUserInfo, userInfoAssertion bool, scope []string, assetPrefix string) *oidc.UserInfo { func userInfoToOIDC(user *query.OIDCUserInfo, userInfoAssertion bool, scope []string, assetPrefix string) *oidc.UserInfo {
out := new(oidc.UserInfo) out := &oidc.UserInfo{
Subject: user.User.ID,
}
for _, s := range scope { for _, s := range scope {
switch s { switch s {
case oidc.ScopeOpenID:
out.Subject = user.User.ID
case oidc.ScopeEmail: case oidc.ScopeEmail:
if !userInfoAssertion { if !userInfoAssertion {
continue continue

View File

@ -280,14 +280,18 @@ func Test_userInfoToOIDC(t *testing.T) {
args: args{ args: args{
user: humanUserInfo, user: humanUserInfo,
}, },
want: &oidc.UserInfo{}, want: &oidc.UserInfo{
Subject: "human1",
},
}, },
{ {
name: "machine, empty", name: "machine, empty",
args: args{ args: args{
user: machineUserInfo, user: machineUserInfo,
}, },
want: &oidc.UserInfo{}, want: &oidc.UserInfo{
Subject: "machine1",
},
}, },
{ {
name: "human, scope openid", name: "human, scope openid",
@ -317,6 +321,7 @@ func Test_userInfoToOIDC(t *testing.T) {
scope: []string{oidc.ScopeEmail}, scope: []string{oidc.ScopeEmail},
}, },
want: &oidc.UserInfo{ want: &oidc.UserInfo{
Subject: "human1",
UserInfoEmail: oidc.UserInfoEmail{ UserInfoEmail: oidc.UserInfoEmail{
Email: "foo@bar.com", Email: "foo@bar.com",
EmailVerified: true, EmailVerified: true,
@ -329,7 +334,9 @@ func Test_userInfoToOIDC(t *testing.T) {
user: humanUserInfo, user: humanUserInfo,
scope: []string{oidc.ScopeEmail}, scope: []string{oidc.ScopeEmail},
}, },
want: &oidc.UserInfo{}, want: &oidc.UserInfo{
Subject: "human1",
},
}, },
{ {
name: "machine, scope email, profileInfoAssertion", name: "machine, scope email, profileInfoAssertion",
@ -338,7 +345,7 @@ func Test_userInfoToOIDC(t *testing.T) {
scope: []string{oidc.ScopeEmail}, scope: []string{oidc.ScopeEmail},
}, },
want: &oidc.UserInfo{ want: &oidc.UserInfo{
UserInfoEmail: oidc.UserInfoEmail{}, Subject: "machine1",
}, },
}, },
{ {
@ -349,6 +356,7 @@ func Test_userInfoToOIDC(t *testing.T) {
scope: []string{oidc.ScopeProfile}, scope: []string{oidc.ScopeProfile},
}, },
want: &oidc.UserInfo{ want: &oidc.UserInfo{
Subject: "human1",
UserInfoProfile: oidc.UserInfoProfile{ UserInfoProfile: oidc.UserInfoProfile{
Name: "xxx", Name: "xxx",
GivenName: "user", GivenName: "user",
@ -370,6 +378,7 @@ func Test_userInfoToOIDC(t *testing.T) {
scope: []string{oidc.ScopeProfile}, scope: []string{oidc.ScopeProfile},
}, },
want: &oidc.UserInfo{ want: &oidc.UserInfo{
Subject: "machine1",
UserInfoProfile: oidc.UserInfoProfile{ UserInfoProfile: oidc.UserInfoProfile{
Name: "machine", Name: "machine",
UpdatedAt: oidc.FromTime(time.Unix(567, 890)), UpdatedAt: oidc.FromTime(time.Unix(567, 890)),
@ -383,7 +392,9 @@ func Test_userInfoToOIDC(t *testing.T) {
user: machineUserInfo, user: machineUserInfo,
scope: []string{oidc.ScopeProfile}, scope: []string{oidc.ScopeProfile},
}, },
want: &oidc.UserInfo{}, want: &oidc.UserInfo{
Subject: "machine1",
},
}, },
{ {
name: "human, scope phone, profileInfoAssertion", name: "human, scope phone, profileInfoAssertion",
@ -393,6 +404,7 @@ func Test_userInfoToOIDC(t *testing.T) {
scope: []string{oidc.ScopePhone}, scope: []string{oidc.ScopePhone},
}, },
want: &oidc.UserInfo{ want: &oidc.UserInfo{
Subject: "human1",
UserInfoPhone: oidc.UserInfoPhone{ UserInfoPhone: oidc.UserInfoPhone{
PhoneNumber: "+31123456789", PhoneNumber: "+31123456789",
PhoneNumberVerified: true, PhoneNumberVerified: true,
@ -405,7 +417,9 @@ func Test_userInfoToOIDC(t *testing.T) {
user: humanUserInfo, user: humanUserInfo,
scope: []string{oidc.ScopePhone}, scope: []string{oidc.ScopePhone},
}, },
want: &oidc.UserInfo{}, want: &oidc.UserInfo{
Subject: "human1",
},
}, },
{ {
name: "machine, scope phone", name: "machine, scope phone",
@ -414,6 +428,7 @@ func Test_userInfoToOIDC(t *testing.T) {
scope: []string{oidc.ScopePhone}, scope: []string{oidc.ScopePhone},
}, },
want: &oidc.UserInfo{ want: &oidc.UserInfo{
Subject: "machine1",
UserInfoPhone: oidc.UserInfoPhone{}, UserInfoPhone: oidc.UserInfoPhone{},
}, },
}, },
@ -424,6 +439,8 @@ func Test_userInfoToOIDC(t *testing.T) {
scope: []string{ScopeUserMetaData}, scope: []string{ScopeUserMetaData},
}, },
want: &oidc.UserInfo{ want: &oidc.UserInfo{
Subject: "human1",
UserInfoEmail: oidc.UserInfoEmail{},
Claims: map[string]any{ Claims: map[string]any{
ClaimUserMetaData: map[string]string{ ClaimUserMetaData: map[string]string{
"key1": base64.RawURLEncoding.EncodeToString([]byte{1, 2, 3}), "key1": base64.RawURLEncoding.EncodeToString([]byte{1, 2, 3}),
@ -438,7 +455,9 @@ func Test_userInfoToOIDC(t *testing.T) {
user: machineUserInfo, user: machineUserInfo,
scope: []string{ScopeUserMetaData}, scope: []string{ScopeUserMetaData},
}, },
want: &oidc.UserInfo{}, want: &oidc.UserInfo{
Subject: "machine1",
},
}, },
{ {
name: "machine, scope resource owner", name: "machine, scope resource owner",
@ -447,6 +466,7 @@ func Test_userInfoToOIDC(t *testing.T) {
scope: []string{ScopeResourceOwner}, scope: []string{ScopeResourceOwner},
}, },
want: &oidc.UserInfo{ want: &oidc.UserInfo{
Subject: "machine1",
Claims: map[string]any{ Claims: map[string]any{
ClaimResourceOwnerID: "orgID", ClaimResourceOwnerID: "orgID",
ClaimResourceOwnerName: "orgName", ClaimResourceOwnerName: "orgName",
@ -461,6 +481,7 @@ func Test_userInfoToOIDC(t *testing.T) {
scope: []string{domain.OrgDomainPrimaryScope + "foo.com"}, scope: []string{domain.OrgDomainPrimaryScope + "foo.com"},
}, },
want: &oidc.UserInfo{ want: &oidc.UserInfo{
Subject: "human1",
Claims: map[string]any{ Claims: map[string]any{
domain.OrgDomainPrimaryClaim: "foo.com", domain.OrgDomainPrimaryClaim: "foo.com",
}, },
@ -473,6 +494,7 @@ func Test_userInfoToOIDC(t *testing.T) {
scope: []string{domain.OrgIDScope + "orgID"}, scope: []string{domain.OrgIDScope + "orgID"},
}, },
want: &oidc.UserInfo{ want: &oidc.UserInfo{
Subject: "machine1",
Claims: map[string]any{ Claims: map[string]any{
domain.OrgIDClaim: "orgID", domain.OrgIDClaim: "orgID",
ClaimResourceOwnerID: "orgID", ClaimResourceOwnerID: "orgID",

View File

@ -6,7 +6,6 @@ import (
"time" "time"
"github.com/go-jose/go-jose/v4" "github.com/go-jose/go-jose/v4"
"github.com/shopspring/decimal"
"github.com/zitadel/logging" "github.com/zitadel/logging"
"github.com/zitadel/saml/pkg/provider/key" "github.com/zitadel/saml/pkg/provider/key"
@ -77,7 +76,7 @@ func (p *Storage) getCertificateAndKey(ctx context.Context, usage crypto.KeyUsag
return p.certificateToCertificateAndKey(selectCertificate(certs.Certificates)) return p.certificateToCertificateAndKey(selectCertificate(certs.Certificates))
} }
var position decimal.Decimal var position float64
if certs.State != nil { if certs.State != nil {
position = certs.State.Position position = certs.State.Position
} }
@ -88,7 +87,7 @@ func (p *Storage) getCertificateAndKey(ctx context.Context, usage crypto.KeyUsag
func (p *Storage) refreshCertificate( func (p *Storage) refreshCertificate(
ctx context.Context, ctx context.Context,
usage crypto.KeyUsage, usage crypto.KeyUsage,
position decimal.Decimal, position float64,
) error { ) error {
ok, err := p.ensureIsLatestCertificate(ctx, position) ok, err := p.ensureIsLatestCertificate(ctx, position)
if err != nil { if err != nil {
@ -104,12 +103,12 @@ func (p *Storage) refreshCertificate(
return nil return nil
} }
func (p *Storage) ensureIsLatestCertificate(ctx context.Context, position decimal.Decimal) (bool, error) { func (p *Storage) ensureIsLatestCertificate(ctx context.Context, position float64) (bool, error) {
maxSequence, err := p.getMaxKeySequence(ctx) maxSequence, err := p.getMaxKeySequence(ctx)
if err != nil { if err != nil {
return false, fmt.Errorf("error retrieving new events: %w", err) return false, fmt.Errorf("error retrieving new events: %w", err)
} }
return position.GreaterThanOrEqual(maxSequence), nil return position >= maxSequence, nil
} }
func (p *Storage) lockAndGenerateCertificateAndKey(ctx context.Context, usage crypto.KeyUsage) error { func (p *Storage) lockAndGenerateCertificateAndKey(ctx context.Context, usage crypto.KeyUsage) error {
@ -152,9 +151,9 @@ func (p *Storage) lockAndGenerateCertificateAndKey(ctx context.Context, usage cr
} }
} }
func (p *Storage) getMaxKeySequence(ctx context.Context) (decimal.Decimal, error) { func (p *Storage) getMaxKeySequence(ctx context.Context) (float64, error) {
return p.eventstore.LatestPosition(ctx, return p.eventstore.LatestSequence(ctx,
eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxPosition). eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxSequence).
ResourceOwner(authz.GetInstance(ctx).InstanceID()). ResourceOwner(authz.GetInstance(ctx).InstanceID()).
AwaitOpenTransactions(). AwaitOpenTransactions().
AddQuery(). AddQuery().

View File

@ -55,13 +55,10 @@ type Storage struct {
} }
func (p *Storage) GetEntityByID(ctx context.Context, entityID string) (*serviceprovider.ServiceProvider, error) { func (p *Storage) GetEntityByID(ctx context.Context, entityID string) (*serviceprovider.ServiceProvider, error) {
app, err := p.query.AppBySAMLEntityID(ctx, entityID) app, err := p.query.ActiveAppBySAMLEntityID(ctx, entityID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if app.State != domain.AppStateActive {
return nil, zerrors.ThrowPreconditionFailed(nil, "SAML-sdaGg", "app is not active")
}
return serviceprovider.NewServiceProvider( return serviceprovider.NewServiceProvider(
app.ID, app.ID,
&serviceprovider.Config{ &serviceprovider.Config{
@ -72,13 +69,10 @@ func (p *Storage) GetEntityByID(ctx context.Context, entityID string) (*servicep
} }
func (p *Storage) GetEntityIDByAppID(ctx context.Context, appID string) (string, error) { func (p *Storage) GetEntityIDByAppID(ctx context.Context, appID string) (string, error) {
app, err := p.query.AppByID(ctx, appID) app, err := p.query.AppByID(ctx, appID, true)
if err != nil { if err != nil {
return "", err return "", err
} }
if app.State != domain.AppStateActive {
return "", zerrors.ThrowPreconditionFailed(nil, "SAML-sdaGg", "app is not active")
}
return app.SAMLConfig.EntityID, nil return app.SAMLConfig.EntityID, nil
} }
@ -137,6 +131,9 @@ func (p *Storage) SetUserinfoWithUserID(ctx context.Context, applicationID strin
if err != nil { if err != nil {
return err return err
} }
if user.State != domain.UserStateActive {
return zerrors.ThrowPreconditionFailed(nil, "SAML-S3gFd", "Errors.User.NotActive")
}
userGrants, err := p.getGrants(ctx, userID, applicationID) userGrants, err := p.getGrants(ctx, userID, applicationID)
if err != nil { if err != nil {
@ -163,6 +160,9 @@ func (p *Storage) SetUserinfoWithLoginName(ctx context.Context, userinfo models.
if err != nil { if err != nil {
return err return err
} }
if user.State != domain.UserStateActive {
return zerrors.ThrowPreconditionFailed(nil, "SAML-FJ262", "Errors.User.NotActive")
}
setUserinfo(user, userinfo, attributes, map[string]*customAttribute{}) setUserinfo(user, userinfo, attributes, map[string]*customAttribute{})
return nil return nil
@ -330,10 +330,15 @@ func (p *Storage) getGrants(ctx context.Context, userID, applicationID string) (
if err != nil { if err != nil {
return nil, err return nil, err
} }
activeQuery, err := query.NewUserGrantStateQuery(domain.UserGrantStateActive)
if err != nil {
return nil, err
}
return p.query.UserGrants(ctx, &query.UserGrantsQueries{ return p.query.UserGrants(ctx, &query.UserGrantsQueries{
Queries: []query.SearchQuery{ Queries: []query.SearchQuery{
projectQuery, projectQuery,
userIDQuery, userIDQuery,
activeQuery,
}, },
}, true) }, true)
} }

View File

@ -1092,7 +1092,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
} }
if !user.IsEmailVerified { if !user.IsEmailVerified {
steps = append(steps, &domain.VerifyEMailStep{ steps = append(steps, &domain.VerifyEMailStep{
InitPassword: !user.PasswordSet, InitPassword: !user.PasswordSet && len(idps.Links) == 0,
}) })
} }
if user.UsernameChangeRequired { if user.UsernameChangeRequired {

View File

@ -1058,6 +1058,74 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
[]domain.NextStep{&domain.VerifyInviteStep{}}, []domain.NextStep{&domain.VerifyInviteStep{}},
nil, nil,
}, },
{
"password not set (email not verified), verify email with password step",
fields{
userSessionViewProvider: &mockViewUserSession{},
userViewProvider: &mockViewUser{
PasswordInitRequired: true,
},
userEventProvider: &mockEventUser{},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &query.LockoutPolicy{
ShowFailures: true,
},
},
orgViewProvider: &mockViewOrg{State: domain.OrgStateActive},
idpUserLinksProvider: &mockIDPUserLinks{},
},
args{
&domain.AuthRequest{
UserID: "UserID",
LoginPolicy: &domain.LoginPolicy{
AllowUsernamePassword: true,
},
},
false,
},
[]domain.NextStep{&domain.VerifyEMailStep{InitPassword: true}},
nil,
},
{
"password not set, but idp, email not verified, verify email step",
fields{
userSessionViewProvider: &mockViewUserSession{
ExternalLoginVerification: testNow.Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{},
userEventProvider: &mockEventUser{},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &query.LockoutPolicy{
ShowFailures: true,
},
},
orgViewProvider: &mockViewOrg{State: domain.OrgStateActive},
idpUserLinksProvider: &mockIDPUserLinks{
[]*query.IDPUserLink{
{
IDPID: "idpID",
UserID: "userID",
IDPName: "idpName",
ProvidedUserID: "providedUserID",
ProvidedUsername: "providedUsername",
},
},
},
},
args{
&domain.AuthRequest{
UserID: "UserID",
LoginPolicy: &domain.LoginPolicy{
AllowUsernamePassword: true,
ExternalLoginCheckLifetime: 10 * 24 * time.Hour,
},
SelectedIDPConfigID: "idpID",
},
false,
},
[]domain.NextStep{&domain.VerifyEMailStep{}},
nil,
},
{ {
"password not set (email not verified), init password step", "password not set (email not verified), init password step",
fields{ fields{

View File

@ -11,6 +11,7 @@ import (
sd "github.com/zitadel/zitadel/internal/config/systemdefaults" sd "github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
eventstore2 "github.com/zitadel/zitadel/internal/eventstore" eventstore2 "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/id" "github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
@ -119,7 +120,11 @@ func (q queryViewWrapper) UserGrantsByProjectAndUserID(ctx context.Context, proj
if err != nil { if err != nil {
return nil, err return nil, err
} }
queries := &query.UserGrantsQueries{Queries: []query.SearchQuery{userGrantUserID, userGrantProjectID}} activeQuery, err := query.NewUserGrantStateQuery(domain.UserGrantStateActive)
if err != nil {
return nil, err
}
queries := &query.UserGrantsQueries{Queries: []query.SearchQuery{userGrantUserID, userGrantProjectID, activeQuery}}
grants, err := q.Queries.UserGrants(ctx, queries, true) grants, err := q.Queries.UserGrants(ctx, queries, true)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -144,7 +144,7 @@ func (c *Commands) CreateOIDCSessionFromDeviceAuth(ctx context.Context, deviceCo
return nil, DeviceAuthStateError(deviceAuthModel.State) return nil, DeviceAuthStateError(deviceAuthModel.State)
} }
cmd, err := c.newOIDCSessionAddEvents(ctx, deviceAuthModel.UserOrgID) cmd, err := c.newOIDCSessionAddEvents(ctx, deviceAuthModel.UserID, deviceAuthModel.UserOrgID)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -126,7 +126,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
pushErr := errors.New("pushErr") pushErr := errors.New("pushErr")
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -149,7 +149,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
{ {
name: "not found error", name: "not found error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect(t, eventstore: expectEventstore(
expectFilter(), expectFilter(),
), ),
}, },
@ -169,7 +169,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
{ {
name: "push error", name: "push error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect(t, eventstore: expectEventstore(
expectFilter(eventFromEventPusherWithInstanceID( expectFilter(eventFromEventPusherWithInstanceID(
"instance1", "instance1",
deviceauth.NewAddedEvent( deviceauth.NewAddedEvent(
@ -211,7 +211,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
{ {
name: "success", name: "success",
fields: fields{ fields: fields{
eventstore: eventstoreExpect(t, eventstore: expectEventstore(
expectFilter(eventFromEventPusherWithInstanceID( expectFilter(eventFromEventPusherWithInstanceID(
"instance1", "instance1",
deviceauth.NewAddedEvent( deviceauth.NewAddedEvent(
@ -256,7 +256,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := &Commands{ c := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
} }
gotDetails, err := c.ApproveDeviceAuth(tt.args.ctx, tt.args.id, tt.args.userID, tt.args.userOrgID, tt.args.authMethods, tt.args.authTime, tt.args.preferredLanguage, tt.args.userAgent, tt.args.sessionID) gotDetails, err := c.ApproveDeviceAuth(tt.args.ctx, tt.args.id, tt.args.userID, tt.args.userOrgID, tt.args.authMethods, tt.args.authTime, tt.args.preferredLanguage, tt.args.userAgent, tt.args.sessionID)
require.ErrorIs(t, err, tt.wantErr) require.ErrorIs(t, err, tt.wantErr)
@ -271,7 +271,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
pushErr := errors.New("pushErr") pushErr := errors.New("pushErr")
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -288,7 +288,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
{ {
name: "not found error", name: "not found error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect(t, eventstore: expectEventstore(
expectFilter(), expectFilter(),
), ),
}, },
@ -298,7 +298,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
{ {
name: "push error", name: "push error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect(t, eventstore: expectEventstore(
expectFilter(eventFromEventPusherWithInstanceID( expectFilter(eventFromEventPusherWithInstanceID(
"instance1", "instance1",
deviceauth.NewAddedEvent( deviceauth.NewAddedEvent(
@ -323,7 +323,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
{ {
name: "success/denied", name: "success/denied",
fields: fields{ fields: fields{
eventstore: eventstoreExpect(t, eventstore: expectEventstore(
expectFilter(eventFromEventPusherWithInstanceID( expectFilter(eventFromEventPusherWithInstanceID(
"instance1", "instance1",
deviceauth.NewAddedEvent( deviceauth.NewAddedEvent(
@ -350,7 +350,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
{ {
name: "success/expired", name: "success/expired",
fields: fields{ fields: fields{
eventstore: eventstoreExpect(t, eventstore: expectEventstore(
expectFilter(eventFromEventPusherWithInstanceID( expectFilter(eventFromEventPusherWithInstanceID(
"instance1", "instance1",
deviceauth.NewAddedEvent( deviceauth.NewAddedEvent(
@ -378,7 +378,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := &Commands{ c := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
} }
gotDetails, err := c.CancelDeviceAuth(tt.args.ctx, tt.args.id, tt.args.reason) gotDetails, err := c.CancelDeviceAuth(tt.args.ctx, tt.args.id, tt.args.reason)
require.ErrorIs(t, err, tt.wantErr) require.ErrorIs(t, err, tt.wantErr)
@ -586,6 +586,69 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
}, },
wantErr: DeviceAuthStateError(domain.DeviceAuthStateDone), wantErr: DeviceAuthStateError(domain.DeviceAuthStateDone),
}, },
{
name: "user not active",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusherWithInstanceID(
"instance1",
deviceauth.NewAddedEvent(
ctx,
deviceauth.NewAggregate("123", "instance1"),
"clientID", "123", "456", time.Now().Add(-time.Minute),
[]string{"openid", "offline_access"},
[]string{"audience"}, false,
),
),
eventFromEventPusherWithInstanceID(
"instance1",
deviceauth.NewApprovedEvent(ctx,
deviceauth.NewAggregate("123", "instance1"),
"userID", "org1",
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
testNow, &language.Afrikaans, &domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}},
},
"sessionID",
),
),
),
expectFilter(
user.NewHumanAddedEvent(
ctx,
&user.NewAggregate("userID", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.English,
domain.GenderUnspecified,
"email",
false,
),
user.NewUserDeactivatedEvent(
ctx,
&user.NewAggregate("userID", "org1").Aggregate,
),
),
),
idGenerator: mock.NewIDGeneratorExpectIDs(t),
defaultAccessTokenLifetime: time.Hour,
defaultRefreshTokenLifetime: 7 * 24 * time.Hour,
defaultRefreshTokenIdleLifetime: 24 * time.Hour,
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx,
"123",
},
wantErr: zerrors.ThrowPreconditionFailed(nil, "OIDCS-kj3g2", "Errors.User.NotActive"),
},
{ {
name: "approved, success", name: "approved, success",
fields: fields{ fields: fields{
@ -617,6 +680,21 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
), ),
), ),
), ),
expectFilter(
user.NewHumanAddedEvent(
ctx,
&user.NewAggregate("userID", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.English,
domain.GenderUnspecified,
"email",
false,
),
),
expectFilter(), // token lifetime expectFilter(), // token lifetime
expectPush( expectPush(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate, oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -699,6 +777,21 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
), ),
), ),
), ),
expectFilter(
user.NewHumanAddedEvent(
ctx,
&user.NewAggregate("userID", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.English,
domain.GenderUnspecified,
"email",
false,
),
),
expectFilter(), // token lifetime expectFilter(), // token lifetime
expectPush( expectPush(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate, oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,

View File

@ -104,6 +104,7 @@ func (wm *IAMSMTPConfigWriteModel) Reduce() error {
wm.reduceSMTPConfigRemovedEvent(e) wm.reduceSMTPConfigRemovedEvent(e)
case *instance.SMTPConfigActivatedEvent: case *instance.SMTPConfigActivatedEvent:
if wm.ID != e.ID { if wm.ID != e.ID {
wm.State = domain.SMTPConfigStateInactive
continue continue
} }
wm.State = domain.SMTPConfigStateActive wm.State = domain.SMTPConfigStateActive

View File

@ -80,7 +80,7 @@ func (c *Commands) CreateOIDCSessionFromAuthRequest(ctx context.Context, authReq
return nil, "", err return nil, "", err
} }
cmd, err := c.newOIDCSessionAddEvents(ctx, sessionModel.UserResourceOwner) cmd, err := c.newOIDCSessionAddEvents(ctx, sessionModel.UserID, sessionModel.UserResourceOwner)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
@ -141,7 +141,7 @@ func (c *Commands) CreateOIDCSession(ctx context.Context,
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()
cmd, err := c.newOIDCSessionAddEvents(ctx, resourceOwner) cmd, err := c.newOIDCSessionAddEvents(ctx, userID, resourceOwner)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -265,7 +265,14 @@ func (c *Commands) RevokeOIDCSessionToken(ctx context.Context, token, clientID s
return c.pushAppendAndReduce(ctx, writeModel, oidcsession.NewAccessTokenRevokedEvent(ctx, writeModel.aggregate)) return c.pushAppendAndReduce(ctx, writeModel, oidcsession.NewAccessTokenRevokedEvent(ctx, writeModel.aggregate))
} }
func (c *Commands) newOIDCSessionAddEvents(ctx context.Context, resourceOwner string, pending ...eventstore.Command) (*OIDCSessionEvents, error) { func (c *Commands) newOIDCSessionAddEvents(ctx context.Context, userID, resourceOwner string, pending ...eventstore.Command) (*OIDCSessionEvents, error) {
userStateModel, err := c.userStateWriteModel(ctx, userID)
if err != nil {
return nil, err
}
if !userStateModel.UserState.IsEnabled() {
return nil, zerrors.ThrowPreconditionFailed(nil, "OIDCS-kj3g2", "Errors.User.NotActive")
}
accessTokenLifetime, refreshTokenLifeTime, refreshTokenIdleLifetime, err := c.tokenTokenLifetimes(ctx) accessTokenLifetime, refreshTokenLifeTime, refreshTokenIdleLifetime, err := c.tokenTokenLifetimes(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
@ -281,6 +288,7 @@ func (c *Commands) newOIDCSessionAddEvents(ctx context.Context, resourceOwner st
encryptionAlg: c.keyAlgorithm, encryptionAlg: c.keyAlgorithm,
events: pending, events: pending,
oidcSessionWriteModel: NewOIDCSessionWriteModel(sessionID, resourceOwner), oidcSessionWriteModel: NewOIDCSessionWriteModel(sessionID, resourceOwner),
userStateModel: userStateModel,
accessTokenLifetime: accessTokenLifetime, accessTokenLifetime: accessTokenLifetime,
refreshTokenLifeTime: refreshTokenLifeTime, refreshTokenLifeTime: refreshTokenLifeTime,
refreshTokenIdleLifetime: refreshTokenIdleLifetime, refreshTokenIdleLifetime: refreshTokenIdleLifetime,
@ -321,6 +329,13 @@ func (c *Commands) newOIDCSessionUpdateEvents(ctx context.Context, refreshToken
if err = sessionWriteModel.CheckRefreshToken(refreshTokenID); err != nil { if err = sessionWriteModel.CheckRefreshToken(refreshTokenID); err != nil {
return nil, err return nil, err
} }
userStateWriteModel, err := c.userStateWriteModel(ctx, sessionWriteModel.UserID)
if err != nil {
return nil, err
}
if !userStateWriteModel.UserState.IsEnabled() {
return nil, zerrors.ThrowPreconditionFailed(nil, "OIDCS-J39h2", "Errors.User.NotActive")
}
accessTokenLifetime, refreshTokenLifeTime, refreshTokenIdleLifetime, err := c.tokenTokenLifetimes(ctx) accessTokenLifetime, refreshTokenLifeTime, refreshTokenIdleLifetime, err := c.tokenTokenLifetimes(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
@ -342,6 +357,7 @@ type OIDCSessionEvents struct {
encryptionAlg crypto.EncryptionAlgorithm encryptionAlg crypto.EncryptionAlgorithm
events []eventstore.Command events []eventstore.Command
oidcSessionWriteModel *OIDCSessionWriteModel oidcSessionWriteModel *OIDCSessionWriteModel
userStateModel *UserV2WriteModel
accessTokenLifetime time.Duration accessTokenLifetime time.Duration
refreshTokenLifeTime time.Duration refreshTokenLifeTime time.Duration

View File

@ -205,6 +205,103 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
err: zerrors.ThrowPreconditionFailed(nil, "COMMAND-Flk38", "Errors.Session.NotExisting"), err: zerrors.ThrowPreconditionFailed(nil, "COMMAND-Flk38", "Errors.Session.NotExisting"),
}, },
}, },
{
"user not active",
fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
authrequest.NewAddedEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate,
"loginClient",
"clientID",
"redirectURI",
"state",
"nonce",
[]string{"openid", "offline_access"},
[]string{"audience"},
domain.OIDCResponseTypeCode,
domain.OIDCResponseModeQuery,
&domain.OIDCCodeChallenge{
Challenge: "challenge",
Method: domain.CodeChallengeMethodS256,
},
[]domain.Prompt{domain.PromptNone},
[]string{"en", "de"},
gu.Ptr(time.Duration(0)),
gu.Ptr("loginHint"),
gu.Ptr("hintUserID"),
true,
),
),
eventFromEventPusher(
authrequest.NewCodeAddedEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate),
),
eventFromEventPusher(
authrequest.NewSessionLinkedEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate,
"sessionID",
"userID",
testNow,
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
),
),
),
expectFilter(
eventFromEventPusher(
session.NewAddedEvent(context.Background(),
&session.NewAggregate("sessionID", "instance1").Aggregate,
&domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}},
},
),
),
eventFromEventPusher(
session.NewUserCheckedEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate,
"userID", "org1", testNow, &language.Afrikaans),
),
eventFromEventPusher(
session.NewPasswordCheckedEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate,
testNow),
),
),
expectFilter(
user.NewHumanAddedEvent(
context.Background(),
&user.NewAggregate("userID", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.Afrikaans,
domain.GenderUnspecified,
"email",
false,
),
user.NewUserDeactivatedEvent(
context.Background(),
&user.NewAggregate("userID", "org1").Aggregate,
),
),
),
idGenerator: mock.NewIDGeneratorExpectIDs(t),
defaultAccessTokenLifetime: time.Hour,
defaultRefreshTokenLifetime: 7 * 24 * time.Hour,
defaultRefreshTokenIdleLifetime: 24 * time.Hour,
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
authRequestID: "V2_authRequestID",
complianceCheck: mockAuthRequestComplianceChecker(nil),
needRefreshToken: true,
},
res{
err: zerrors.ThrowPreconditionFailed(nil, "OIDCS-kj3g2", "Errors.User.NotActive"),
},
},
{ {
"add successful", "add successful",
fields{ fields{
@ -266,6 +363,21 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
testNow), testNow),
), ),
), ),
expectFilter(
user.NewHumanAddedEvent(
context.Background(),
&user.NewAggregate("userID", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.Afrikaans,
domain.GenderUnspecified,
"email",
false,
),
),
expectFilter(), // token lifetime expectFilter(), // token lifetime
expectPush( expectPush(
authrequest.NewCodeExchangedEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate), authrequest.NewCodeExchangedEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate),
@ -382,6 +494,21 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
testNow), testNow),
), ),
), ),
expectFilter(
user.NewHumanAddedEvent(
context.Background(),
&user.NewAggregate("userID", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.Afrikaans,
domain.GenderUnspecified,
"email",
false,
),
),
expectFilter(), // token lifetime expectFilter(), // token lifetime
expectPush( expectPush(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate, oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -521,10 +648,81 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
}, },
wantErr: io.ErrClosedPipe, wantErr: io.ErrClosedPipe,
}, },
{
name: "not active user",
fields: fields{
eventstore: expectEventstore(
expectFilter(
user.NewHumanAddedEvent(
context.Background(),
&user.NewAggregate("userID", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.Afrikaans,
domain.GenderUnspecified,
"email",
false,
),
user.NewUserDeactivatedEvent(
context.Background(),
&user.NewAggregate("userID", "org1").Aggregate,
),
),
),
idGenerator: mock.NewIDGeneratorExpectIDs(t),
defaultAccessTokenLifetime: time.Hour,
defaultRefreshTokenLifetime: 7 * 24 * time.Hour,
defaultRefreshTokenIdleLifetime: 24 * time.Hour,
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
userID: "userID",
resourceOwner: "org1",
clientID: "clientID",
audience: []string{"audience"},
scope: []string{"openid", "offline_access"},
authMethods: []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
authTime: testNow,
nonce: "nonce",
preferredLanguage: &language.Afrikaans,
userAgent: &domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}},
},
reason: domain.TokenReasonAuthRequest,
actor: &domain.TokenActor{
UserID: "user2",
Issuer: "foo.com",
},
needRefreshToken: false,
},
wantErr: zerrors.ThrowPreconditionFailed(nil, "OIDCS-kj3g2", "Errors.User.NotActive"),
},
{ {
name: "without refresh token", name: "without refresh token",
fields: fields{ fields: fields{
eventstore: expectEventstore( eventstore: expectEventstore(
expectFilter(
user.NewHumanAddedEvent(
context.Background(),
&user.NewAggregate("userID", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.Afrikaans,
domain.GenderUnspecified,
"email",
false,
),
),
expectFilter(), // token lifetime expectFilter(), // token lifetime
expectPush( expectPush(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate, oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -606,6 +804,21 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
name: "with refresh token", name: "with refresh token",
fields: fields{ fields: fields{
eventstore: expectEventstore( eventstore: expectEventstore(
expectFilter(
user.NewHumanAddedEvent(
context.Background(),
&user.NewAggregate("userID", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.Afrikaans,
domain.GenderUnspecified,
"email",
false,
),
),
expectFilter(), // token lifetime expectFilter(), // token lifetime
expectPush( expectPush(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate, oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -689,6 +902,21 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
name: "with sessionID", name: "with sessionID",
fields: fields{ fields: fields{
eventstore: expectEventstore( eventstore: expectEventstore(
expectFilter(
user.NewHumanAddedEvent(
context.Background(),
&user.NewAggregate("userID", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.Afrikaans,
domain.GenderUnspecified,
"email",
false,
),
),
expectFilter(), // token lifetime expectFilter(), // token lifetime
expectPush( expectPush(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate, oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -772,6 +1000,21 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
name: "impersonation not allowed", name: "impersonation not allowed",
fields: fields{ fields: fields{
eventstore: expectEventstore( eventstore: expectEventstore(
expectFilter(
user.NewHumanAddedEvent(
context.Background(),
&user.NewAggregate("userID", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.Afrikaans,
domain.GenderUnspecified,
"email",
false,
),
),
expectFilter(), // token lifetime expectFilter(), // token lifetime
), ),
idGenerator: mock.NewIDGeneratorExpectIDs(t, "oidcSessionID"), idGenerator: mock.NewIDGeneratorExpectIDs(t, "oidcSessionID"),
@ -813,6 +1056,21 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
name: "impersonation allowed", name: "impersonation allowed",
fields: fields{ fields: fields{
eventstore: expectEventstore( eventstore: expectEventstore(
expectFilter(
user.NewHumanAddedEvent(
context.Background(),
&user.NewAggregate("userID", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.Afrikaans,
domain.GenderUnspecified,
"email",
false,
),
),
expectFilter(), // token lifetime expectFilter(), // token lifetime
expectPush( expectPush(
user.NewUserImpersonatedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate, "clientID", &domain.TokenActor{ user.NewUserImpersonatedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate, "clientID", &domain.TokenActor{
@ -1067,6 +1325,63 @@ func TestCommands_ExchangeOIDCSessionRefreshAndAccessToken(t *testing.T) {
err: zerrors.ThrowPreconditionFailed(nil, "OIDCS-3jt2w", "Errors.OIDCSession.RefreshTokenInvalid"), err: zerrors.ThrowPreconditionFailed(nil, "OIDCS-3jt2w", "Errors.OIDCSession.RefreshTokenInvalid"),
}, },
}, },
{
"user not active",
fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusherWithCreationDateNow(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
"userID", "org1", "sessionID", "clientID", []string{"audience"}, []string{"openid", "profile", "offline_access"},
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow, "nonce", &language.Afrikaans,
&domain.UserAgent{FingerprintID: gu.Ptr("browserFP")},
),
),
eventFromEventPusherWithCreationDateNow(
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
"at_accessTokenID", []string{"openid", "profile", "offline_access"}, time.Hour, domain.TokenReasonAuthRequest, nil),
),
eventFromEventPusherWithCreationDateNow(
oidcsession.NewRefreshTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
"rt_refreshTokenID", 7*24*time.Hour, 24*time.Hour),
),
),
expectFilter(
user.NewHumanAddedEvent(
context.Background(),
&user.NewAggregate("userID", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.Afrikaans,
domain.GenderUnspecified,
"email",
false,
),
user.NewUserDeactivatedEvent(
context.Background(),
&user.NewAggregate("userID", "org1").Aggregate,
),
),
),
idGenerator: mock.NewIDGeneratorExpectIDs(t),
defaultAccessTokenLifetime: time.Hour,
defaultRefreshTokenLifetime: 7 * 24 * time.Hour,
defaultRefreshTokenIdleLifetime: 24 * time.Hour,
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
refreshToken: "VjJfb2lkY1Nlc3Npb25JRC1ydF9yZWZyZXNoVG9rZW5JRDp1c2VySUQ", //V2_oidcSessionID:rt_refreshTokenID:userID
scope: []string{"openid", "offline_access"},
complianceCheck: mockRefreshTokenComplianceChecker(nil),
},
res{
err: zerrors.ThrowPreconditionFailed(nil, "OIDCS-J39h2", "Errors.User.NotActive"),
},
},
{ {
"refresh successful", "refresh successful",
fields{ fields{
@ -1088,6 +1403,21 @@ func TestCommands_ExchangeOIDCSessionRefreshAndAccessToken(t *testing.T) {
"rt_refreshTokenID", 7*24*time.Hour, 24*time.Hour), "rt_refreshTokenID", 7*24*time.Hour, 24*time.Hour),
), ),
), ),
expectFilter(
user.NewHumanAddedEvent(
context.Background(),
&user.NewAggregate("userID", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.Afrikaans,
domain.GenderUnspecified,
"email",
false,
),
),
expectFilter(), // token lifetime expectFilter(), // token lifetime
expectPush( expectPush(
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate, oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1153,7 +1483,7 @@ func TestCommands_ExchangeOIDCSessionRefreshAndAccessToken(t *testing.T) {
func TestCommands_OIDCSessionByRefreshToken(t *testing.T) { func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
defaultAccessTokenLifetime time.Duration defaultAccessTokenLifetime time.Duration
defaultRefreshTokenLifetime time.Duration defaultRefreshTokenLifetime time.Duration
@ -1177,7 +1507,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
{ {
"invalid refresh token format error", "invalid refresh token format error",
fields{ fields{
eventstore: eventstoreExpect(t), eventstore: expectEventstore(),
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
}, },
args{ args{
@ -1191,7 +1521,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
{ {
"inactive session error", "inactive session error",
fields{ fields{
eventstore: eventstoreExpect(t, eventstore: expectEventstore(
expectFilter(), expectFilter(),
), ),
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
@ -1207,7 +1537,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
{ {
"invalid refresh token error", "invalid refresh token error",
fields{ fields{
eventstore: eventstoreExpect(t, eventstore: expectEventstore(
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate, oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1235,7 +1565,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
{ {
"expired refresh token error", "expired refresh token error",
fields{ fields{
eventstore: eventstoreExpect(t, eventstore: expectEventstore(
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate, oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1267,7 +1597,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
{ {
"get successful", "get successful",
fields{ fields{
eventstore: eventstoreExpect(t, eventstore: expectEventstore(
expectFilter( expectFilter(
eventFromEventPusherWithCreationDateNow( eventFromEventPusherWithCreationDateNow(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate, oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1316,7 +1646,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := &Commands{ c := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
defaultAccessTokenLifetime: tt.fields.defaultAccessTokenLifetime, defaultAccessTokenLifetime: tt.fields.defaultAccessTokenLifetime,
defaultRefreshTokenLifetime: tt.fields.defaultRefreshTokenLifetime, defaultRefreshTokenLifetime: tt.fields.defaultRefreshTokenLifetime,
@ -1348,7 +1678,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
func TestCommands_RevokeOIDCSessionToken(t *testing.T) { func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
keyAlgorithm crypto.EncryptionAlgorithm keyAlgorithm crypto.EncryptionAlgorithm
} }
type args struct { type args struct {
@ -1368,7 +1698,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
{ {
"invalid token", "invalid token",
fields{ fields{
eventstore: eventstoreExpect(t), eventstore: expectEventstore(),
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
}, },
args{ args{
@ -1382,7 +1712,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
{ {
"refresh_token inactive", "refresh_token inactive",
fields{ fields{
eventstore: eventstoreExpect(t, eventstore: expectEventstore(
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate, oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1407,7 +1737,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
{ {
"refresh_token invalid client", "refresh_token invalid client",
fields{ fields{
eventstore: eventstoreExpect(t, eventstore: expectEventstore(
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate, oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1432,7 +1762,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
{ {
"refresh_token revoked", "refresh_token revoked",
fields{ fields{
eventstore: eventstoreExpect(t, eventstore: expectEventstore(
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate, oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1468,7 +1798,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
{ {
"access_token inactive session", "access_token inactive session",
fields{ fields{
eventstore: eventstoreExpect(t, eventstore: expectEventstore(
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate, oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1493,7 +1823,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
{ {
"access_token invalid client", "access_token invalid client",
fields{ fields{
eventstore: eventstoreExpect(t, eventstore: expectEventstore(
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate, oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1518,7 +1848,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
{ {
"access_token revoked", "access_token revoked",
fields{ fields{
eventstore: eventstoreExpect(t, eventstore: expectEventstore(
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate, oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1555,7 +1885,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := &Commands{ c := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
keyAlgorithm: tt.fields.keyAlgorithm, keyAlgorithm: tt.fields.keyAlgorithm,
} }
err := c.RevokeOIDCSessionToken(tt.args.ctx, tt.args.token, tt.args.clientID) err := c.RevokeOIDCSessionToken(tt.args.ctx, tt.args.token, tt.args.clientID)

View File

@ -93,6 +93,7 @@ func (wm *IAMSMSConfigWriteModel) Reduce() error {
} }
case *instance.SMSConfigTwilioActivatedEvent: case *instance.SMSConfigTwilioActivatedEvent:
if wm.ID != e.ID { if wm.ID != e.ID {
wm.State = domain.SMSConfigStateInactive
continue continue
} }
wm.State = domain.SMSConfigStateActive wm.State = domain.SMSConfigStateActive
@ -110,6 +111,7 @@ func (wm *IAMSMSConfigWriteModel) Reduce() error {
wm.State = domain.SMSConfigStateRemoved wm.State = domain.SMSConfigStateRemoved
case *instance.SMSConfigActivatedEvent: case *instance.SMSConfigActivatedEvent:
if wm.ID != e.ID { if wm.ID != e.ID {
wm.State = domain.SMSConfigStateInactive
continue continue
} }
wm.State = domain.SMSConfigStateActive wm.State = domain.SMSConfigStateActive

View File

@ -7,8 +7,6 @@ import (
"strings" "strings"
"time" "time"
pgxdecimal "github.com/jackc/pgx-shopspring-decimal"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
"github.com/jackc/pgx/v5/stdlib" "github.com/jackc/pgx/v5/stdlib"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
@ -84,11 +82,6 @@ func (c *Config) Connect(useAdmin bool, pusherRatio, spoolerRatio float64, purpo
return nil, err return nil, err
} }
config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
pgxdecimal.Register(conn.TypeMap())
return nil
}
if connConfig.MaxOpenConns != 0 { if connConfig.MaxOpenConns != 0 {
config.MaxConns = int32(connConfig.MaxOpenConns) config.MaxConns = int32(connConfig.MaxOpenConns)
} }

View File

@ -7,8 +7,6 @@ import (
"strings" "strings"
"time" "time"
pgxdecimal "github.com/jackc/pgx-shopspring-decimal"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
"github.com/jackc/pgx/v5/stdlib" "github.com/jackc/pgx/v5/stdlib"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
@ -84,10 +82,6 @@ func (c *Config) Connect(useAdmin bool, pusherRatio, spoolerRatio float64, purpo
if err != nil { if err != nil {
return nil, err return nil, err
} }
config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
pgxdecimal.Register(conn.TypeMap())
return nil
}
if connConfig.MaxOpenConns != 0 { if connConfig.MaxOpenConns != 0 {
config.MaxConns = int32(connConfig.MaxOpenConns) config.MaxConns = int32(connConfig.MaxOpenConns)

View File

@ -5,8 +5,6 @@ import (
"reflect" "reflect"
"time" "time"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
@ -46,7 +44,7 @@ type Event interface {
// CreatedAt is the time the event was created at // CreatedAt is the time the event was created at
CreatedAt() time.Time CreatedAt() time.Time
// Position is the global position of the event // Position is the global position of the event
Position() decimal.Decimal Position() float64
// Unmarshal parses the payload and stores the result // Unmarshal parses the payload and stores the result
// in the value pointed to by ptr. If ptr is nil or not a pointer, // in the value pointed to by ptr. If ptr is nil or not a pointer,

View File

@ -5,8 +5,6 @@ import (
"encoding/json" "encoding/json"
"time" "time"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/service" "github.com/zitadel/zitadel/internal/api/service"
) )
@ -23,7 +21,7 @@ type BaseEvent struct {
Agg *Aggregate Agg *Aggregate
Seq uint64 Seq uint64
Pos decimal.Decimal Pos float64
Creation time.Time Creation time.Time
previousAggregateSequence uint64 previousAggregateSequence uint64
previousAggregateTypeSequence uint64 previousAggregateTypeSequence uint64
@ -36,7 +34,7 @@ type BaseEvent struct {
} }
// Position implements Event. // Position implements Event.
func (e *BaseEvent) Position() decimal.Decimal { func (e *BaseEvent) Position() float64 {
return e.Pos return e.Pos
} }

View File

@ -8,7 +8,6 @@ import (
"time" "time"
"github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgconn"
"github.com/shopspring/decimal"
"github.com/zitadel/logging" "github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
@ -218,11 +217,11 @@ func (es *Eventstore) FilterToReducer(ctx context.Context, searchQuery *SearchQu
}) })
} }
// LatestPosition filters the latest position for the given search query // LatestSequence filters the latest sequence for the given search query
func (es *Eventstore) LatestPosition(ctx context.Context, queryFactory *SearchQueryBuilder) (decimal.Decimal, error) { func (es *Eventstore) LatestSequence(ctx context.Context, queryFactory *SearchQueryBuilder) (float64, error) {
queryFactory.InstanceID(authz.GetInstance(ctx).InstanceID()) queryFactory.InstanceID(authz.GetInstance(ctx).InstanceID())
return es.querier.LatestPosition(ctx, queryFactory) return es.querier.LatestSequence(ctx, queryFactory)
} }
// InstanceIDs returns the instance ids found by the search query // InstanceIDs returns the instance ids found by the search query
@ -267,8 +266,8 @@ type Querier interface {
Health(ctx context.Context) error Health(ctx context.Context) error
// FilterToReducer calls r for every event returned from the storage // FilterToReducer calls r for every event returned from the storage
FilterToReducer(ctx context.Context, searchQuery *SearchQueryBuilder, r Reducer) error FilterToReducer(ctx context.Context, searchQuery *SearchQueryBuilder, r Reducer) error
// LatestPosition returns the latest position found by the search query // LatestSequence returns the latest sequence found by the search query
LatestPosition(ctx context.Context, queryFactory *SearchQueryBuilder) (decimal.Decimal, error) LatestSequence(ctx context.Context, queryFactory *SearchQueryBuilder) (float64, error)
// InstanceIDs returns the instance ids found by the search query // InstanceIDs returns the instance ids found by the search query
InstanceIDs(ctx context.Context, queryFactory *SearchQueryBuilder) ([]string, error) InstanceIDs(ctx context.Context, queryFactory *SearchQueryBuilder) ([]string, error)
} }

View File

@ -4,8 +4,6 @@ import (
"context" "context"
"testing" "testing"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
) )
@ -100,7 +98,7 @@ func TestCRDB_Filter(t *testing.T) {
} }
} }
func TestCRDB_LatestPosition(t *testing.T) { func TestCRDB_LatestSequence(t *testing.T) {
type args struct { type args struct {
searchQuery *eventstore.SearchQueryBuilder searchQuery *eventstore.SearchQueryBuilder
} }
@ -108,7 +106,7 @@ func TestCRDB_LatestPosition(t *testing.T) {
existingEvents []eventstore.Command existingEvents []eventstore.Command
} }
type res struct { type res struct {
position decimal.Decimal sequence float64
} }
tests := []struct { tests := []struct {
name string name string
@ -120,7 +118,7 @@ func TestCRDB_LatestPosition(t *testing.T) {
{ {
name: "aggregate type filter no sequence", name: "aggregate type filter no sequence",
args: args{ args: args{
searchQuery: eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxPosition). searchQuery: eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxSequence).
AddQuery(). AddQuery().
AggregateTypes("not found"). AggregateTypes("not found").
Builder(), Builder(),
@ -137,7 +135,7 @@ func TestCRDB_LatestPosition(t *testing.T) {
{ {
name: "aggregate type filter sequence", name: "aggregate type filter sequence",
args: args{ args: args{
searchQuery: eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxPosition). searchQuery: eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxSequence).
AddQuery(). AddQuery().
AggregateTypes(eventstore.AggregateType(t.Name())). AggregateTypes(eventstore.AggregateType(t.Name())).
Builder(), Builder(),
@ -171,12 +169,12 @@ func TestCRDB_LatestPosition(t *testing.T) {
return return
} }
position, err := db.LatestPosition(context.Background(), tt.args.searchQuery) sequence, err := db.LatestSequence(context.Background(), tt.args.searchQuery)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("CRDB.query() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("CRDB.query() error = %v, wantErr %v", err, tt.wantErr)
} }
if tt.res.position.GreaterThan(position) { if tt.res.sequence > sequence {
t.Errorf("CRDB.query() expected sequence: %v got %v", tt.res.position, position) t.Errorf("CRDB.query() expected sequence: %v got %v", tt.res.sequence, sequence)
} }
}) })
} }

View File

@ -9,7 +9,6 @@ import (
"time" "time"
"github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgconn"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/service" "github.com/zitadel/zitadel/internal/api/service"
@ -391,7 +390,7 @@ func (repo *testPusher) Push(ctx context.Context, commands ...Command) (events [
type testQuerier struct { type testQuerier struct {
events []Event events []Event
sequence decimal.Decimal sequence float64
instances []string instances []string
err error err error
t *testing.T t *testing.T
@ -424,9 +423,9 @@ func (repo *testQuerier) FilterToReducer(ctx context.Context, searchQuery *Searc
return nil return nil
} }
func (repo *testQuerier) LatestPosition(ctx context.Context, queryFactory *SearchQueryBuilder) (decimal.Decimal, error) { func (repo *testQuerier) LatestSequence(ctx context.Context, queryFactory *SearchQueryBuilder) (float64, error) {
if repo.err != nil { if repo.err != nil {
return decimal.Decimal{}, repo.err return 0, repo.err
} }
return repo.sequence, nil return repo.sequence, nil
} }
@ -1056,7 +1055,7 @@ func TestEventstore_FilterEvents(t *testing.T) {
} }
} }
func TestEventstore_LatestPosition(t *testing.T) { func TestEventstore_LatestSequence(t *testing.T) {
type args struct { type args struct {
query *SearchQueryBuilder query *SearchQueryBuilder
} }
@ -1076,7 +1075,7 @@ func TestEventstore_LatestPosition(t *testing.T) {
name: "no events", name: "no events",
args: args{ args: args{
query: &SearchQueryBuilder{ query: &SearchQueryBuilder{
columns: ColumnsMaxPosition, columns: ColumnsMaxSequence,
queries: []*SearchQuery{ queries: []*SearchQuery{
{ {
builder: &SearchQueryBuilder{}, builder: &SearchQueryBuilder{},
@ -1099,7 +1098,7 @@ func TestEventstore_LatestPosition(t *testing.T) {
name: "repo error", name: "repo error",
args: args{ args: args{
query: &SearchQueryBuilder{ query: &SearchQueryBuilder{
columns: ColumnsMaxPosition, columns: ColumnsMaxSequence,
queries: []*SearchQuery{ queries: []*SearchQuery{
{ {
builder: &SearchQueryBuilder{}, builder: &SearchQueryBuilder{},
@ -1122,7 +1121,7 @@ func TestEventstore_LatestPosition(t *testing.T) {
name: "found events", name: "found events",
args: args{ args: args{
query: &SearchQueryBuilder{ query: &SearchQueryBuilder{
columns: ColumnsMaxPosition, columns: ColumnsMaxSequence,
queries: []*SearchQuery{ queries: []*SearchQuery{
{ {
builder: &SearchQueryBuilder{}, builder: &SearchQueryBuilder{},
@ -1148,7 +1147,7 @@ func TestEventstore_LatestPosition(t *testing.T) {
querier: tt.fields.repo, querier: tt.fields.repo,
} }
_, err := es.LatestPosition(context.Background(), tt.args.query) _, err := es.LatestSequence(context.Background(), tt.args.query)
if (err != nil) != tt.res.wantErr { if (err != nil) != tt.res.wantErr {
t.Errorf("Eventstore.aggregatesToEvents() error = %v, wantErr %v", err, tt.res.wantErr) t.Errorf("Eventstore.aggregatesToEvents() error = %v, wantErr %v", err, tt.res.wantErr)
} }

View File

@ -8,7 +8,6 @@ import (
"time" "time"
"github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgconn"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
) )
@ -124,7 +123,7 @@ func (h *FieldHandler) processEvents(ctx context.Context, config *triggerConfig)
return additionalIteration, err return additionalIteration, err
} }
// stop execution if currentState.eventTimestamp >= config.maxCreatedAt // stop execution if currentState.eventTimestamp >= config.maxCreatedAt
if !config.maxPosition.IsZero() && currentState.position.GreaterThanOrEqual(config.maxPosition) { if config.maxPosition != 0 && currentState.position >= config.maxPosition {
return false, nil return false, nil
} }
@ -157,7 +156,7 @@ func (h *FieldHandler) fetchEvents(ctx context.Context, tx *sql.Tx, currentState
idx, offset := skipPreviouslyReducedEvents(events, currentState) idx, offset := skipPreviouslyReducedEvents(events, currentState)
if currentState.position.Equal(events[len(events)-1].Position()) { if currentState.position == events[len(events)-1].Position() {
offset += currentState.offset offset += currentState.offset
} }
currentState.position = events[len(events)-1].Position() currentState.position = events[len(events)-1].Position()
@ -187,9 +186,9 @@ func (h *FieldHandler) fetchEvents(ctx context.Context, tx *sql.Tx, currentState
} }
func skipPreviouslyReducedEvents(events []eventstore.Event, currentState *state) (index int, offset uint32) { func skipPreviouslyReducedEvents(events []eventstore.Event, currentState *state) (index int, offset uint32) {
var position decimal.Decimal var position float64
for i, event := range events { for i, event := range events {
if !event.Position().Equal(position) { if event.Position() != position {
offset = 0 offset = 0
position = event.Position() position = event.Position()
} }

View File

@ -4,13 +4,13 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"math"
"math/rand" "math/rand"
"slices" "slices"
"sync" "sync"
"time" "time"
"github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgconn"
"github.com/shopspring/decimal"
"github.com/zitadel/logging" "github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
@ -379,7 +379,7 @@ func (h *Handler) existingInstances(ctx context.Context) ([]string, error) {
type triggerConfig struct { type triggerConfig struct {
awaitRunning bool awaitRunning bool
maxPosition decimal.Decimal maxPosition float64
} }
type TriggerOpt func(conf *triggerConfig) type TriggerOpt func(conf *triggerConfig)
@ -390,7 +390,7 @@ func WithAwaitRunning() TriggerOpt {
} }
} }
func WithMaxPosition(position decimal.Decimal) TriggerOpt { func WithMaxPosition(position float64) TriggerOpt {
return func(conf *triggerConfig) { return func(conf *triggerConfig) {
conf.maxPosition = position conf.maxPosition = position
} }
@ -500,7 +500,7 @@ func (h *Handler) processEvents(ctx context.Context, config *triggerConfig) (add
return additionalIteration, err return additionalIteration, err
} }
// stop execution if currentState.eventTimestamp >= config.maxCreatedAt // stop execution if currentState.eventTimestamp >= config.maxCreatedAt
if !config.maxPosition.Equal(decimal.Decimal{}) && currentState.position.GreaterThanOrEqual(config.maxPosition) { if config.maxPosition != 0 && currentState.position >= config.maxPosition {
return false, nil return false, nil
} }
@ -576,7 +576,7 @@ func (h *Handler) generateStatements(ctx context.Context, tx *sql.Tx, currentSta
func skipPreviouslyReducedStatements(statements []*Statement, currentState *state) int { func skipPreviouslyReducedStatements(statements []*Statement, currentState *state) int {
for i, statement := range statements { for i, statement := range statements {
if statement.Position.Equal(currentState.position) && if statement.Position == currentState.position &&
statement.AggregateID == currentState.aggregateID && statement.AggregateID == currentState.aggregateID &&
statement.AggregateType == currentState.aggregateType && statement.AggregateType == currentState.aggregateType &&
statement.Sequence == currentState.sequence { statement.Sequence == currentState.sequence {
@ -609,14 +609,14 @@ func (h *Handler) executeStatement(ctx context.Context, tx *sql.Tx, currentState
return nil return nil
} }
_, err = tx.ExecContext(ctx, "SAVEPOINT exec") _, err = tx.Exec("SAVEPOINT exec")
if err != nil { if err != nil {
h.log().WithError(err).Debug("create savepoint failed") h.log().WithError(err).Debug("create savepoint failed")
return err return err
} }
var shouldContinue bool var shouldContinue bool
defer func() { defer func() {
_, errSave := tx.ExecContext(ctx, "RELEASE SAVEPOINT exec") _, errSave := tx.Exec("RELEASE SAVEPOINT exec")
if err == nil { if err == nil {
err = errSave err = errSave
} }
@ -644,8 +644,9 @@ func (h *Handler) eventQuery(currentState *state) *eventstore.SearchQueryBuilder
OrderAsc(). OrderAsc().
InstanceID(currentState.instanceID) InstanceID(currentState.instanceID)
if currentState.position.GreaterThan(decimal.Decimal{}) { if currentState.position > 0 {
builder = builder.PositionGreaterEqual(currentState.position) // decrease position by 10 because builder.PositionAfter filters for position > and we need position >=
builder = builder.PositionAfter(math.Float64frombits(math.Float64bits(currentState.position) - 10))
if currentState.offset > 0 { if currentState.offset > 0 {
builder = builder.Offset(currentState.offset) builder = builder.Offset(currentState.offset)
} }

View File

@ -7,8 +7,6 @@ import (
"errors" "errors"
"time" "time"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
@ -16,7 +14,7 @@ import (
type state struct { type state struct {
instanceID string instanceID string
position decimal.Decimal position float64
eventTimestamp time.Time eventTimestamp time.Time
aggregateType eventstore.AggregateType aggregateType eventstore.AggregateType
aggregateID string aggregateID string
@ -47,7 +45,7 @@ func (h *Handler) currentState(ctx context.Context, tx *sql.Tx, config *triggerC
aggregateType = new(sql.NullString) aggregateType = new(sql.NullString)
sequence = new(sql.NullInt64) sequence = new(sql.NullInt64)
timestamp = new(sql.NullTime) timestamp = new(sql.NullTime)
position = new(decimal.NullDecimal) position = new(sql.NullFloat64)
offset = new(sql.NullInt64) offset = new(sql.NullInt64)
) )
@ -77,7 +75,7 @@ func (h *Handler) currentState(ctx context.Context, tx *sql.Tx, config *triggerC
currentState.aggregateType = eventstore.AggregateType(aggregateType.String) currentState.aggregateType = eventstore.AggregateType(aggregateType.String)
currentState.sequence = uint64(sequence.Int64) currentState.sequence = uint64(sequence.Int64)
currentState.eventTimestamp = timestamp.Time currentState.eventTimestamp = timestamp.Time
currentState.position = position.Decimal currentState.position = position.Float64
// psql does not provide unsigned numbers so we work around it // psql does not provide unsigned numbers so we work around it
currentState.offset = uint32(offset.Int64) currentState.offset = uint32(offset.Int64)
return currentState, nil return currentState, nil

View File

@ -11,7 +11,6 @@ import (
"time" "time"
"github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgconn"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/database/mock" "github.com/zitadel/zitadel/internal/database/mock"
@ -167,7 +166,7 @@ func TestHandler_updateLastUpdated(t *testing.T) {
updatedState: &state{ updatedState: &state{
instanceID: "instance", instanceID: "instance",
eventTimestamp: time.Now(), eventTimestamp: time.Now(),
position: decimal.NewFromInt(42), position: 42,
}, },
}, },
isErr: func(t *testing.T, err error) { isErr: func(t *testing.T, err error) {
@ -193,7 +192,7 @@ func TestHandler_updateLastUpdated(t *testing.T) {
updatedState: &state{ updatedState: &state{
instanceID: "instance", instanceID: "instance",
eventTimestamp: time.Now(), eventTimestamp: time.Now(),
position: decimal.NewFromInt(42), position: 42,
}, },
}, },
isErr: func(t *testing.T, err error) { isErr: func(t *testing.T, err error) {
@ -218,7 +217,7 @@ func TestHandler_updateLastUpdated(t *testing.T) {
eventstore.AggregateType("aggregate type"), eventstore.AggregateType("aggregate type"),
uint64(42), uint64(42),
mock.AnyType[time.Time]{}, mock.AnyType[time.Time]{},
decimal.NewFromInt(42), float64(42),
uint32(0), uint32(0),
), ),
mock.WithExecRowsAffected(1), mock.WithExecRowsAffected(1),
@ -229,7 +228,7 @@ func TestHandler_updateLastUpdated(t *testing.T) {
updatedState: &state{ updatedState: &state{
instanceID: "instance", instanceID: "instance",
eventTimestamp: time.Now(), eventTimestamp: time.Now(),
position: decimal.NewFromInt(42), position: 42,
aggregateType: "aggregate type", aggregateType: "aggregate type",
aggregateID: "aggregate id", aggregateID: "aggregate id",
sequence: 42, sequence: 42,
@ -398,7 +397,7 @@ func TestHandler_currentState(t *testing.T) {
"aggregate type", "aggregate type",
int64(42), int64(42),
testTime, testTime,
decimal.NewFromInt(42).String(), float64(42),
uint16(10), uint16(10),
}, },
}, },
@ -413,7 +412,7 @@ func TestHandler_currentState(t *testing.T) {
currentState: &state{ currentState: &state{
instanceID: "instance", instanceID: "instance",
eventTimestamp: testTime, eventTimestamp: testTime,
position: decimal.NewFromInt(42), position: 42,
aggregateType: "aggregate type", aggregateType: "aggregate type",
aggregateID: "aggregate id", aggregateID: "aggregate id",
sequence: 42, sequence: 42,

View File

@ -9,7 +9,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/shopspring/decimal"
"github.com/zitadel/logging" "github.com/zitadel/logging"
"golang.org/x/exp/constraints" "golang.org/x/exp/constraints"
@ -84,7 +83,7 @@ type Statement struct {
AggregateType eventstore.AggregateType AggregateType eventstore.AggregateType
AggregateID string AggregateID string
Sequence uint64 Sequence uint64
Position decimal.Decimal Position float64
CreationDate time.Time CreationDate time.Time
InstanceID string InstanceID string

View File

@ -2,16 +2,13 @@ package eventstore_test
import ( import (
"context" "context"
"database/sql"
"encoding/json" "encoding/json"
"os" "os"
"testing" "testing"
"time" "time"
"github.com/cockroachdb/cockroach-go/v2/testserver" "github.com/cockroachdb/cockroach-go/v2/testserver"
pgxdecimal "github.com/jackc/pgx-shopspring-decimal"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/jackc/pgx/v5/stdlib"
"github.com/zitadel/logging" "github.com/zitadel/logging"
"github.com/zitadel/zitadel/cmd/initialise" "github.com/zitadel/zitadel/cmd/initialise"
@ -42,19 +39,10 @@ func TestMain(m *testing.M) {
testCRDBClient = &database.DB{ testCRDBClient = &database.DB{
Database: new(testDB), Database: new(testDB),
} }
config, err := pgxpool.ParseConfig(ts.PGURL().String()) testCRDBClient.DB, err = sql.Open("postgres", ts.PGURL().String())
if err != nil { if err != nil {
logging.WithFields("error", err).Fatal("unable to parse db config") logging.WithFields("error", err).Fatal("unable to connect to db")
} }
config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
pgxdecimal.Register(conn.TypeMap())
return nil
}
pool, err := pgxpool.NewWithConfig(context.Background(), config)
if err != nil {
logging.WithFields("error", err).Fatal("unable to create db pool")
}
testCRDBClient.DB = stdlib.OpenDBFromPool(pool)
if err = testCRDBClient.Ping(); err != nil { if err = testCRDBClient.Ping(); err != nil {
logging.WithFields("error", err).Fatal("unable to ping db") logging.WithFields("error", err).Fatal("unable to ping db")
} }
@ -115,19 +103,10 @@ func initDB(db *database.DB) error {
} }
func connectLocalhost() (*database.DB, error) { func connectLocalhost() (*database.DB, error) {
config, err := pgxpool.ParseConfig("postgresql://root@localhost:26257/defaultdb?sslmode=disable") client, err := sql.Open("pgx", "postgresql://root@localhost:26257/defaultdb?sslmode=disable")
if err != nil { if err != nil {
return nil, err return nil, err
} }
config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
pgxdecimal.Register(conn.TypeMap())
return nil
}
pool, err := pgxpool.NewWithConfig(context.Background(), config)
if err != nil {
return nil, err
}
client := stdlib.OpenDBFromPool(pool)
if err = client.Ping(); err != nil { if err = client.Ping(); err != nil {
return nil, err return nil, err
} }

View File

@ -1,23 +1,19 @@
package eventstore package eventstore
import ( import "time"
"time"
"github.com/shopspring/decimal"
)
// ReadModel is the minimum representation of a read model. // ReadModel is the minimum representation of a read model.
// It implements a basic reducer // It implements a basic reducer
// it might be saved in a database or in memory // it might be saved in a database or in memory
type ReadModel struct { type ReadModel struct {
AggregateID string `json:"-"` AggregateID string `json:"-"`
ProcessedSequence uint64 `json:"-"` ProcessedSequence uint64 `json:"-"`
CreationDate time.Time `json:"-"` CreationDate time.Time `json:"-"`
ChangeDate time.Time `json:"-"` ChangeDate time.Time `json:"-"`
Events []Event `json:"-"` Events []Event `json:"-"`
ResourceOwner string `json:"-"` ResourceOwner string `json:"-"`
InstanceID string `json:"-"` InstanceID string `json:"-"`
Position decimal.Decimal `json:"-"` Position float64 `json:"-"`
} }
// AppendEvents adds all the events to the read model. // AppendEvents adds all the events to the read model.

View File

@ -5,8 +5,6 @@ import (
"encoding/json" "encoding/json"
"time" "time"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
) )
@ -20,7 +18,7 @@ type Event struct {
// Seq is the sequence of the event // Seq is the sequence of the event
Seq uint64 Seq uint64
// Pos is the global sequence of the event multiple events can have the same sequence // Pos is the global sequence of the event multiple events can have the same sequence
Pos decimal.Decimal Pos float64
//CreationDate is the time the event is created //CreationDate is the time the event is created
// it's used for human readability. // it's used for human readability.
@ -93,7 +91,7 @@ func (e *Event) Sequence() uint64 {
} }
// Position implements [eventstore.Event] // Position implements [eventstore.Event]
func (e *Event) Position() decimal.Decimal { func (e *Event) Position() float64 {
return e.Pos return e.Pos
} }

View File

@ -13,7 +13,6 @@ import (
context "context" context "context"
reflect "reflect" reflect "reflect"
decimal "github.com/shopspring/decimal"
eventstore "github.com/zitadel/zitadel/internal/eventstore" eventstore "github.com/zitadel/zitadel/internal/eventstore"
gomock "go.uber.org/mock/gomock" gomock "go.uber.org/mock/gomock"
) )
@ -84,19 +83,19 @@ func (mr *MockQuerierMockRecorder) InstanceIDs(arg0, arg1 any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIDs", reflect.TypeOf((*MockQuerier)(nil).InstanceIDs), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIDs", reflect.TypeOf((*MockQuerier)(nil).InstanceIDs), arg0, arg1)
} }
// LatestPosition mocks base method. // LatestSequence mocks base method.
func (m *MockQuerier) LatestPosition(arg0 context.Context, arg1 *eventstore.SearchQueryBuilder) (decimal.Decimal, error) { func (m *MockQuerier) LatestSequence(arg0 context.Context, arg1 *eventstore.SearchQueryBuilder) (float64, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LatestPosition", arg0, arg1) ret := m.ctrl.Call(m, "LatestSequence", arg0, arg1)
ret0, _ := ret[0].(decimal.Decimal) ret0, _ := ret[0].(float64)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// LatestPosition indicates an expected call of LatestPosition. // LatestSequence indicates an expected call of LatestSequence.
func (mr *MockQuerierMockRecorder) LatestPosition(arg0, arg1 any) *gomock.Call { func (mr *MockQuerierMockRecorder) LatestSequence(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LatestPosition", reflect.TypeOf((*MockQuerier)(nil).LatestPosition), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LatestSequence", reflect.TypeOf((*MockQuerier)(nil).LatestSequence), arg0, arg1)
} }
// MockPusher is a mock of Pusher interface. // MockPusher is a mock of Pusher interface.

View File

@ -7,7 +7,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock" "go.uber.org/mock/gomock"
@ -187,8 +186,8 @@ func (e *mockEvent) Sequence() uint64 {
return e.sequence return e.sequence
} }
func (e *mockEvent) Position() decimal.Decimal { func (e *mockEvent) Position() float64 {
return decimal.Decimal{} return 0
} }
func (e *mockEvent) CreatedAt() time.Time { func (e *mockEvent) CreatedAt() time.Time {

View File

@ -55,8 +55,6 @@ const (
//OperationNotIn checks if a stored value does not match one of the passed value list //OperationNotIn checks if a stored value does not match one of the passed value list
OperationNotIn OperationNotIn
OperationGreaterEqual
operationCount operationCount
) )
@ -234,10 +232,10 @@ func instanceIDsFilter(builder *eventstore.SearchQueryBuilder, query *SearchQuer
} }
func positionAfterFilter(builder *eventstore.SearchQueryBuilder, query *SearchQuery) *Filter { func positionAfterFilter(builder *eventstore.SearchQueryBuilder, query *SearchQuery) *Filter {
if builder.GetPositionAfter().IsZero() { if builder.GetPositionAfter() == 0 {
return nil return nil
} }
query.Position = NewFilter(FieldPosition, builder.GetPositionAfter(), OperationGreaterEqual) query.Position = NewFilter(FieldPosition, builder.GetPositionAfter(), OperationGreater)
return query.Position return query.Position
} }

View File

@ -11,7 +11,6 @@ import (
"github.com/cockroachdb/cockroach-go/v2/crdb" "github.com/cockroachdb/cockroach-go/v2/crdb"
"github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgconn"
"github.com/shopspring/decimal"
"github.com/zitadel/logging" "github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
@ -266,11 +265,11 @@ func (crdb *CRDB) FilterToReducer(ctx context.Context, searchQuery *eventstore.S
return err return err
} }
// LatestPosition returns the latest position found by the search query // LatestSequence returns the latest sequence found by the search query
func (db *CRDB) LatestPosition(ctx context.Context, searchQuery *eventstore.SearchQueryBuilder) (decimal.Decimal, error) { func (db *CRDB) LatestSequence(ctx context.Context, searchQuery *eventstore.SearchQueryBuilder) (float64, error) {
var position decimal.Decimal var position sql.NullFloat64
err := query(ctx, db, searchQuery, &position, false) err := query(ctx, db, searchQuery, &position, false)
return position, err return position.Float64, err
} }
// InstanceIDs returns the instance ids found by the search query // InstanceIDs returns the instance ids found by the search query
@ -337,7 +336,7 @@ func (db *CRDB) eventQuery(useV1 bool) string {
" FROM eventstore.events2" " FROM eventstore.events2"
} }
func (db *CRDB) maxPositionQuery(useV1 bool) string { func (db *CRDB) maxSequenceQuery(useV1 bool) string {
if useV1 { if useV1 {
return `SELECT event_sequence FROM eventstore.events` return `SELECT event_sequence FROM eventstore.events`
} }
@ -415,8 +414,6 @@ func (db *CRDB) operation(operation repository.Operation) string {
return "=" return "="
case repository.OperationGreater: case repository.OperationGreater:
return ">" return ">"
case repository.OperationGreaterEqual:
return ">="
case repository.OperationLess: case repository.OperationLess:
return "<" return "<"
case repository.OperationJSONContains: case repository.OperationJSONContains:

View File

@ -4,8 +4,6 @@ import (
"database/sql" "database/sql"
"testing" "testing"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/repository" "github.com/zitadel/zitadel/internal/eventstore/repository"
) )
@ -314,7 +312,7 @@ func generateEvent(t *testing.T, aggregateID string, opts ...func(*repository.Ev
ResourceOwner: sql.NullString{String: "ro", Valid: true}, ResourceOwner: sql.NullString{String: "ro", Valid: true},
Typ: "test.created", Typ: "test.created",
Version: "v1", Version: "v1",
Pos: decimal.NewFromInt(42), Pos: 42,
} }
for _, opt := range opts { for _, opt := range opts {

View File

@ -9,7 +9,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/shopspring/decimal"
"github.com/zitadel/logging" "github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/call" "github.com/zitadel/zitadel/internal/api/call"
@ -26,7 +25,7 @@ type querier interface {
conditionFormat(repository.Operation) string conditionFormat(repository.Operation) string
placeholder(query string) string placeholder(query string) string
eventQuery(useV1 bool) string eventQuery(useV1 bool) string
maxPositionQuery(useV1 bool) string maxSequenceQuery(useV1 bool) string
instanceIDsQuery(useV1 bool) string instanceIDsQuery(useV1 bool) string
db() *database.DB db() *database.DB
orderByEventSequence(desc, shouldOrderBySequence, useV1 bool) string orderByEventSequence(desc, shouldOrderBySequence, useV1 bool) string
@ -75,7 +74,7 @@ func query(ctx context.Context, criteria querier, searchQuery *eventstore.Search
// instead of using the max function of the database (which doesn't work for postgres) // instead of using the max function of the database (which doesn't work for postgres)
// we select the most recent row // we select the most recent row
if q.Columns == eventstore.ColumnsMaxPosition { if q.Columns == eventstore.ColumnsMaxSequence {
q.Limit = 1 q.Limit = 1
q.Desc = true q.Desc = true
} }
@ -92,7 +91,7 @@ func query(ctx context.Context, criteria querier, searchQuery *eventstore.Search
switch q.Columns { switch q.Columns {
case eventstore.ColumnsEvent, case eventstore.ColumnsEvent,
eventstore.ColumnsMaxPosition: eventstore.ColumnsMaxSequence:
query += criteria.orderByEventSequence(q.Desc, shouldOrderBySequence, useV1) query += criteria.orderByEventSequence(q.Desc, shouldOrderBySequence, useV1)
} }
@ -136,8 +135,8 @@ func query(ctx context.Context, criteria querier, searchQuery *eventstore.Search
func prepareColumns(criteria querier, columns eventstore.Columns, useV1 bool) (string, func(s scan, dest interface{}) error) { func prepareColumns(criteria querier, columns eventstore.Columns, useV1 bool) (string, func(s scan, dest interface{}) error) {
switch columns { switch columns {
case eventstore.ColumnsMaxPosition: case eventstore.ColumnsMaxSequence:
return criteria.maxPositionQuery(useV1), maxPositionScanner return criteria.maxSequenceQuery(useV1), maxSequenceScanner
case eventstore.ColumnsInstanceIDs: case eventstore.ColumnsInstanceIDs:
return criteria.instanceIDsQuery(useV1), instanceIDsScanner return criteria.instanceIDsQuery(useV1), instanceIDsScanner
case eventstore.ColumnsEvent: case eventstore.ColumnsEvent:
@ -155,15 +154,13 @@ func prepareTimeTravel(ctx context.Context, criteria querier, allow bool) string
return criteria.Timetravel(took) return criteria.Timetravel(took)
} }
func maxPositionScanner(row scan, dest interface{}) (err error) { func maxSequenceScanner(row scan, dest interface{}) (err error) {
position, ok := dest.(*decimal.Decimal) position, ok := dest.(*sql.NullFloat64)
if !ok { if !ok {
return zerrors.ThrowInvalidArgumentf(nil, "SQL-NBjA9", "type must be decimal.Decimal got: %T", dest) return zerrors.ThrowInvalidArgumentf(nil, "SQL-NBjA9", "type must be sql.NullInt64 got: %T", dest)
} }
var res decimal.NullDecimal err = row(position)
err = row(&res)
if err == nil || errors.Is(err, sql.ErrNoRows) { if err == nil || errors.Is(err, sql.ErrNoRows) {
*position = res.Decimal
return nil return nil
} }
return zerrors.ThrowInternal(err, "SQL-bN5xg", "something went wrong") return zerrors.ThrowInternal(err, "SQL-bN5xg", "something went wrong")
@ -192,7 +189,7 @@ func eventsScanner(useV1 bool) func(scanner scan, dest interface{}) (err error)
return zerrors.ThrowInvalidArgumentf(nil, "SQL-4GP6F", "events scanner: invalid type %T", dest) return zerrors.ThrowInvalidArgumentf(nil, "SQL-4GP6F", "events scanner: invalid type %T", dest)
} }
event := new(repository.Event) event := new(repository.Event)
position := new(decimal.NullDecimal) position := new(sql.NullFloat64)
if useV1 { if useV1 {
err = scanner( err = scanner(
@ -229,7 +226,7 @@ func eventsScanner(useV1 bool) func(scanner scan, dest interface{}) (err error)
logging.New().WithError(err).Warn("unable to scan row") logging.New().WithError(err).Warn("unable to scan row")
return zerrors.ThrowInternal(err, "SQL-M0dsf", "unable to scan row") return zerrors.ThrowInternal(err, "SQL-M0dsf", "unable to scan row")
} }
event.Pos = position.Decimal event.Pos = position.Float64
return reduce(event) return reduce(event)
} }
} }

View File

@ -10,7 +10,6 @@ import (
"time" "time"
"github.com/DATA-DOG/go-sqlmock" "github.com/DATA-DOG/go-sqlmock"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/database"
@ -110,36 +109,36 @@ func Test_prepareColumns(t *testing.T) {
{ {
name: "max column", name: "max column",
args: args{ args: args{
columns: eventstore.ColumnsMaxPosition, columns: eventstore.ColumnsMaxSequence,
dest: new(decimal.Decimal), dest: new(sql.NullFloat64),
useV1: true, useV1: true,
}, },
res: res{ res: res{
query: `SELECT event_sequence FROM eventstore.events`, query: `SELECT event_sequence FROM eventstore.events`,
expected: decimal.NewFromInt(42), expected: sql.NullFloat64{Float64: 43, Valid: true},
}, },
fields: fields{ fields: fields{
dbRow: []interface{}{decimal.NewNullDecimal(decimal.NewFromInt(42))}, dbRow: []interface{}{sql.NullFloat64{Float64: 43, Valid: true}},
}, },
}, },
{ {
name: "max column v2", name: "max column v2",
args: args{ args: args{
columns: eventstore.ColumnsMaxPosition, columns: eventstore.ColumnsMaxSequence,
dest: new(decimal.Decimal), dest: new(sql.NullFloat64),
}, },
res: res{ res: res{
query: `SELECT "position" FROM eventstore.events2`, query: `SELECT "position" FROM eventstore.events2`,
expected: decimal.NewFromInt(42), expected: sql.NullFloat64{Float64: 43, Valid: true},
}, },
fields: fields{ fields: fields{
dbRow: []interface{}{decimal.NewNullDecimal(decimal.NewFromInt(42))}, dbRow: []interface{}{sql.NullFloat64{Float64: 43, Valid: true}},
}, },
}, },
{ {
name: "max sequence wrong dest type", name: "max sequence wrong dest type",
args: args{ args: args{
columns: eventstore.ColumnsMaxPosition, columns: eventstore.ColumnsMaxSequence,
dest: new(uint64), dest: new(uint64),
}, },
res: res{ res: res{
@ -179,11 +178,11 @@ func Test_prepareColumns(t *testing.T) {
res: res{ res: res{
query: `SELECT created_at, event_type, "sequence", "position", payload, creator, "owner", instance_id, aggregate_type, aggregate_id, revision FROM eventstore.events2`, query: `SELECT created_at, event_type, "sequence", "position", payload, creator, "owner", instance_id, aggregate_type, aggregate_id, revision FROM eventstore.events2`,
expected: []eventstore.Event{ expected: []eventstore.Event{
&repository.Event{AggregateID: "hodor", AggregateType: "user", Seq: 5, Pos: decimal.NewFromInt(42), Data: nil, Version: "v1"}, &repository.Event{AggregateID: "hodor", AggregateType: "user", Seq: 5, Pos: 42, Data: nil, Version: "v1"},
}, },
}, },
fields: fields{ fields: fields{
dbRow: []interface{}{time.Time{}, eventstore.EventType(""), uint64(5), decimal.NewNullDecimal(decimal.NewFromInt(42)), sql.RawBytes(nil), "", sql.NullString{}, "", eventstore.AggregateType("user"), "hodor", uint8(1)}, dbRow: []interface{}{time.Time{}, eventstore.EventType(""), uint64(5), sql.NullFloat64{Float64: 42, Valid: true}, sql.RawBytes(nil), "", sql.NullString{}, "", eventstore.AggregateType("user"), "hodor", uint8(1)},
}, },
}, },
{ {
@ -198,11 +197,11 @@ func Test_prepareColumns(t *testing.T) {
res: res{ res: res{
query: `SELECT created_at, event_type, "sequence", "position", payload, creator, "owner", instance_id, aggregate_type, aggregate_id, revision FROM eventstore.events2`, query: `SELECT created_at, event_type, "sequence", "position", payload, creator, "owner", instance_id, aggregate_type, aggregate_id, revision FROM eventstore.events2`,
expected: []eventstore.Event{ expected: []eventstore.Event{
&repository.Event{AggregateID: "hodor", AggregateType: "user", Seq: 5, Pos: decimal.Decimal{}, Data: nil, Version: "v1"}, &repository.Event{AggregateID: "hodor", AggregateType: "user", Seq: 5, Pos: 0, Data: nil, Version: "v1"},
}, },
}, },
fields: fields{ fields: fields{
dbRow: []interface{}{time.Time{}, eventstore.EventType(""), uint64(5), decimal.NullDecimal{}, sql.RawBytes(nil), "", sql.NullString{}, "", eventstore.AggregateType("user"), "hodor", uint8(1)}, dbRow: []interface{}{time.Time{}, eventstore.EventType(""), uint64(5), sql.NullFloat64{Float64: 0, Valid: false}, sql.RawBytes(nil), "", sql.NullString{}, "", eventstore.AggregateType("user"), "hodor", uint8(1)},
}, },
}, },
{ {

View File

@ -5,8 +5,6 @@ import (
"database/sql" "database/sql"
"time" "time"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
@ -25,7 +23,7 @@ type SearchQueryBuilder struct {
queries []*SearchQuery queries []*SearchQuery
tx *sql.Tx tx *sql.Tx
allowTimeTravel bool allowTimeTravel bool
positionGreaterEqual decimal.Decimal positionAfter float64
awaitOpenTransactions bool awaitOpenTransactions bool
creationDateAfter time.Time creationDateAfter time.Time
creationDateBefore time.Time creationDateBefore time.Time
@ -76,8 +74,8 @@ func (b *SearchQueryBuilder) GetAllowTimeTravel() bool {
return b.allowTimeTravel return b.allowTimeTravel
} }
func (b SearchQueryBuilder) GetPositionAfter() decimal.Decimal { func (b SearchQueryBuilder) GetPositionAfter() float64 {
return b.positionGreaterEqual return b.positionAfter
} }
func (b SearchQueryBuilder) GetAwaitOpenTransactions() bool { func (b SearchQueryBuilder) GetAwaitOpenTransactions() bool {
@ -133,8 +131,8 @@ type Columns int8
const ( const (
//ColumnsEvent represents all fields of an event //ColumnsEvent represents all fields of an event
ColumnsEvent = iota + 1 ColumnsEvent = iota + 1
// ColumnsMaxPosition represents the latest sequence of the filtered events // ColumnsMaxSequence represents the latest sequence of the filtered events
ColumnsMaxPosition ColumnsMaxSequence
// ColumnsInstanceIDs represents the instance ids of the filtered events // ColumnsInstanceIDs represents the instance ids of the filtered events
ColumnsInstanceIDs ColumnsInstanceIDs
@ -269,8 +267,8 @@ func (builder *SearchQueryBuilder) AllowTimeTravel() *SearchQueryBuilder {
} }
// PositionAfter filters for events which happened after the specified time // PositionAfter filters for events which happened after the specified time
func (builder *SearchQueryBuilder) PositionGreaterEqual(position decimal.Decimal) *SearchQueryBuilder { func (builder *SearchQueryBuilder) PositionAfter(position float64) *SearchQueryBuilder {
builder.positionGreaterEqual = position builder.positionAfter = position
return builder return builder
} }

View File

@ -116,10 +116,10 @@ func TestSearchQuerybuilderSetters(t *testing.T) {
{ {
name: "set columns", name: "set columns",
args: args{ args: args{
setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{testSetColumns(ColumnsMaxPosition)}, setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{testSetColumns(ColumnsMaxSequence)},
}, },
res: &SearchQueryBuilder{ res: &SearchQueryBuilder{
columns: ColumnsMaxPosition, columns: ColumnsMaxSequence,
}, },
}, },
{ {

View File

@ -5,8 +5,6 @@ import (
"reflect" "reflect"
"time" "time"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
@ -22,7 +20,7 @@ var _ eventstore.Event = (*Event)(nil)
type Event struct { type Event struct {
ID string ID string
Seq uint64 Seq uint64
Pos decimal.Decimal Pos float64
CreationDate time.Time CreationDate time.Time
Typ eventstore.EventType Typ eventstore.EventType
PreviousSequence uint64 PreviousSequence uint64
@ -82,7 +80,7 @@ func (e *Event) Sequence() uint64 {
} }
// Position implements [eventstore.Event] // Position implements [eventstore.Event]
func (e *Event) Position() decimal.Decimal { func (e *Event) Position() float64 {
return e.Pos return e.Pos
} }

View File

@ -4,7 +4,6 @@ import (
"encoding/json" "encoding/json"
"time" "time"
"github.com/shopspring/decimal"
"github.com/zitadel/logging" "github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
@ -22,7 +21,7 @@ type event struct {
typ eventstore.EventType typ eventstore.EventType
createdAt time.Time createdAt time.Time
sequence uint64 sequence uint64
position decimal.Decimal position float64
payload Payload payload Payload
} }
@ -85,8 +84,8 @@ func (e *event) Sequence() uint64 {
return e.sequence return e.sequence
} }
// Position implements [eventstore.Event] // Sequence implements [eventstore.Event]
func (e *event) Position() decimal.Decimal { func (e *event) Position() float64 {
return e.position return e.position
} }

View File

@ -22,6 +22,7 @@ import (
"github.com/zitadel/zitadel/pkg/grpc/authn" "github.com/zitadel/zitadel/pkg/grpc/authn"
"github.com/zitadel/zitadel/pkg/grpc/management" "github.com/zitadel/zitadel/pkg/grpc/management"
"github.com/zitadel/zitadel/pkg/grpc/user" "github.com/zitadel/zitadel/pkg/grpc/user"
user_v2 "github.com/zitadel/zitadel/pkg/grpc/user/v2"
) )
func (i *Instance) CreateOIDCClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string, appType app.OIDCAppType, authMethod app.OIDCAuthMethodType, devMode bool, grantTypes ...app.OIDCGrantType) (*management.AddOIDCAppResponse, error) { func (i *Instance) CreateOIDCClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string, appType app.OIDCAppType, authMethod app.OIDCAuthMethodType, devMode bool, grantTypes ...app.OIDCGrantType) (*management.AddOIDCAppResponse, error) {
@ -107,6 +108,20 @@ func (i *Instance) CreateOIDCInactivateClient(ctx context.Context, redirectURI,
return client, err return client, err
} }
func (i *Instance) CreateOIDCInactivateProjectClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string) (*management.AddOIDCAppResponse, error) {
client, err := i.CreateOIDCNativeClient(ctx, redirectURI, logoutRedirectURI, projectID, false)
if err != nil {
return nil, err
}
_, err = i.Client.Mgmt.DeactivateProject(ctx, &management.DeactivateProjectRequest{
Id: projectID,
})
if err != nil {
return nil, err
}
return client, err
}
func (i *Instance) CreateOIDCImplicitFlowClient(ctx context.Context, redirectURI string) (*management.AddOIDCAppResponse, error) { func (i *Instance) CreateOIDCImplicitFlowClient(ctx context.Context, redirectURI string) (*management.AddOIDCAppResponse, error) {
project, err := i.Client.Mgmt.AddProject(ctx, &management.AddProjectRequest{ project, err := i.Client.Mgmt.AddProject(ctx, &management.AddProjectRequest{
Name: fmt.Sprintf("project-%d", time.Now().UnixNano()), Name: fmt.Sprintf("project-%d", time.Now().UnixNano()),
@ -341,6 +356,31 @@ func (i *Instance) CreateOIDCCredentialsClient(ctx context.Context) (machine *ma
return machine, name, secret.GetClientId(), secret.GetClientSecret(), nil return machine, name, secret.GetClientId(), secret.GetClientSecret(), nil
} }
func (i *Instance) CreateOIDCCredentialsClientInactive(ctx context.Context) (machine *management.AddMachineUserResponse, name, clientID, clientSecret string, err error) {
name = gofakeit.Username()
machine, err = i.Client.Mgmt.AddMachineUser(ctx, &management.AddMachineUserRequest{
Name: name,
UserName: name,
AccessTokenType: user.AccessTokenType_ACCESS_TOKEN_TYPE_JWT,
})
if err != nil {
return nil, "", "", "", err
}
secret, err := i.Client.Mgmt.GenerateMachineSecret(ctx, &management.GenerateMachineSecretRequest{
UserId: machine.GetUserId(),
})
if err != nil {
return nil, "", "", "", err
}
_, err = i.Client.UserV2.DeactivateUser(ctx, &user_v2.DeactivateUserRequest{
UserId: machine.GetUserId(),
})
if err != nil {
return nil, "", "", "", err
}
return machine, name, secret.GetClientId(), secret.GetClientSecret(), nil
}
func (i *Instance) CreateOIDCJWTProfileClient(ctx context.Context) (machine *management.AddMachineUserResponse, name string, keyData []byte, err error) { func (i *Instance) CreateOIDCJWTProfileClient(ctx context.Context) (machine *management.AddMachineUserResponse, name string, keyData []byte, err error) {
name = gofakeit.Username() name = gofakeit.Username()
machine, err = i.Client.Mgmt.AddMachineUser(ctx, &management.AddMachineUserRequest{ machine, err = i.Client.Mgmt.AddMachineUser(ctx, &management.AddMachineUserRequest{

View File

@ -5,7 +5,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/shopspring/decimal"
"golang.org/x/text/language" "golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
@ -141,7 +140,7 @@ func (q *Queries) accessTokenByOIDCSessionAndTokenID(ctx context.Context, oidcSe
// checkSessionNotTerminatedAfter checks if a [session.TerminateType] event (or user events leading to a session termination) // checkSessionNotTerminatedAfter checks if a [session.TerminateType] event (or user events leading to a session termination)
// occurred after a certain time and will return an error if so. // occurred after a certain time and will return an error if so.
func (q *Queries) checkSessionNotTerminatedAfter(ctx context.Context, sessionID, userID string, position decimal.Decimal, fingerprintID string) (err error) { func (q *Queries) checkSessionNotTerminatedAfter(ctx context.Context, sessionID, userID string, position float64, fingerprintID string) (err error) {
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()
@ -163,7 +162,7 @@ func (q *Queries) checkSessionNotTerminatedAfter(ctx context.Context, sessionID,
} }
type sessionTerminatedModel struct { type sessionTerminatedModel struct {
position decimal.Decimal position float64
sessionID string sessionID string
userID string userID string
fingerPrintID string fingerPrintID string
@ -183,7 +182,7 @@ func (s *sessionTerminatedModel) AppendEvents(events ...eventstore.Event) {
func (s *sessionTerminatedModel) Query() *eventstore.SearchQueryBuilder { func (s *sessionTerminatedModel) Query() *eventstore.SearchQueryBuilder {
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
PositionGreaterEqual(s.position). PositionAfter(s.position).
AddQuery(). AddQuery().
AggregateTypes(session.AggregateType). AggregateTypes(session.AggregateType).
AggregateIDs(s.sessionID). AggregateIDs(s.sessionID).

View File

@ -256,7 +256,7 @@ func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bo
traceSpan.EndWithError(err) traceSpan.EndWithError(err)
} }
stmt, scan := prepareAppQuery(ctx, q.client) stmt, scan := prepareAppQuery(ctx, q.client, false)
eq := sq.Eq{ eq := sq.Eq{
AppColumnID.identifier(): appID, AppColumnID.identifier(): appID,
AppColumnProjectID.identifier(): projectID, AppColumnProjectID.identifier(): projectID,
@ -274,15 +274,20 @@ func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bo
return app, err return app, err
} }
func (q *Queries) AppByID(ctx context.Context, appID string) (app *App, err error) { func (q *Queries) AppByID(ctx context.Context, appID string, activeOnly bool) (app *App, err error) {
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()
stmt, scan := prepareAppQuery(ctx, q.client) stmt, scan := prepareAppQuery(ctx, q.client, activeOnly)
eq := sq.Eq{ eq := sq.Eq{
AppColumnID.identifier(): appID, AppColumnID.identifier(): appID,
AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
} }
if activeOnly {
eq[AppColumnState.identifier()] = domain.AppStateActive
eq[ProjectColumnState.identifier()] = domain.ProjectStateActive
eq[OrgColumnState.identifier()] = domain.OrgStateActive
}
query, args, err := stmt.Where(eq).ToSql() query, args, err := stmt.Where(eq).ToSql()
if err != nil { if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-immt9", "Errors.Query.SQLStatement") return nil, zerrors.ThrowInternal(err, "QUERY-immt9", "Errors.Query.SQLStatement")
@ -295,7 +300,7 @@ func (q *Queries) AppByID(ctx context.Context, appID string) (app *App, err erro
return app, err return app, err
} }
func (q *Queries) AppBySAMLEntityID(ctx context.Context, entityID string) (app *App, err error) { func (q *Queries) ActiveAppBySAMLEntityID(ctx context.Context, entityID string) (app *App, err error) {
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()
@ -303,6 +308,9 @@ func (q *Queries) AppBySAMLEntityID(ctx context.Context, entityID string) (app *
eq := sq.Eq{ eq := sq.Eq{
AppSAMLConfigColumnEntityID.identifier(): entityID, AppSAMLConfigColumnEntityID.identifier(): entityID,
AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
AppColumnState.identifier(): domain.AppStateActive,
ProjectColumnState.identifier(): domain.ProjectStateActive,
OrgColumnState.identifier(): domain.OrgStateActive,
} }
query, args, err := stmt.Where(eq).ToSql() query, args, err := stmt.Where(eq).ToSql()
if err != nil { if err != nil {
@ -413,8 +421,13 @@ func (q *Queries) AppByClientID(ctx context.Context, clientID string) (app *App,
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()
stmt, scan := prepareAppQuery(ctx, q.client) stmt, scan := prepareAppQuery(ctx, q.client, true)
eq := sq.Eq{AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()} eq := sq.Eq{
AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
AppColumnState.identifier(): domain.AppStateActive,
ProjectColumnState.identifier(): domain.ProjectStateActive,
OrgColumnState.identifier(): domain.OrgStateActive,
}
query, args, err := stmt.Where(sq.And{ query, args, err := stmt.Where(sq.And{
eq, eq,
sq.Or{ sq.Or{
@ -491,107 +504,121 @@ func NewAppProjectIDSearchQuery(id string) (SearchQuery, error) {
return NewTextQuery(AppColumnProjectID, id, TextEquals) return NewTextQuery(AppColumnProjectID, id, TextEquals)
} }
func prepareAppQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) { func prepareAppQuery(ctx context.Context, db prepareDatabase, activeOnly bool) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return sq.Select( query := sq.Select(
AppColumnID.identifier(), AppColumnID.identifier(),
AppColumnName.identifier(), AppColumnName.identifier(),
AppColumnProjectID.identifier(), AppColumnProjectID.identifier(),
AppColumnCreationDate.identifier(), AppColumnCreationDate.identifier(),
AppColumnChangeDate.identifier(), AppColumnChangeDate.identifier(),
AppColumnResourceOwner.identifier(), AppColumnResourceOwner.identifier(),
AppColumnState.identifier(), AppColumnState.identifier(),
AppColumnSequence.identifier(), AppColumnSequence.identifier(),
AppAPIConfigColumnAppID.identifier(), AppAPIConfigColumnAppID.identifier(),
AppAPIConfigColumnClientID.identifier(), AppAPIConfigColumnClientID.identifier(),
AppAPIConfigColumnAuthMethod.identifier(), AppAPIConfigColumnAuthMethod.identifier(),
AppOIDCConfigColumnAppID.identifier(), AppOIDCConfigColumnAppID.identifier(),
AppOIDCConfigColumnVersion.identifier(), AppOIDCConfigColumnVersion.identifier(),
AppOIDCConfigColumnClientID.identifier(), AppOIDCConfigColumnClientID.identifier(),
AppOIDCConfigColumnRedirectUris.identifier(), AppOIDCConfigColumnRedirectUris.identifier(),
AppOIDCConfigColumnResponseTypes.identifier(), AppOIDCConfigColumnResponseTypes.identifier(),
AppOIDCConfigColumnGrantTypes.identifier(), AppOIDCConfigColumnGrantTypes.identifier(),
AppOIDCConfigColumnApplicationType.identifier(), AppOIDCConfigColumnApplicationType.identifier(),
AppOIDCConfigColumnAuthMethodType.identifier(), AppOIDCConfigColumnAuthMethodType.identifier(),
AppOIDCConfigColumnPostLogoutRedirectUris.identifier(), AppOIDCConfigColumnPostLogoutRedirectUris.identifier(),
AppOIDCConfigColumnDevMode.identifier(), AppOIDCConfigColumnDevMode.identifier(),
AppOIDCConfigColumnAccessTokenType.identifier(), AppOIDCConfigColumnAccessTokenType.identifier(),
AppOIDCConfigColumnAccessTokenRoleAssertion.identifier(), AppOIDCConfigColumnAccessTokenRoleAssertion.identifier(),
AppOIDCConfigColumnIDTokenRoleAssertion.identifier(), AppOIDCConfigColumnIDTokenRoleAssertion.identifier(),
AppOIDCConfigColumnIDTokenUserinfoAssertion.identifier(), AppOIDCConfigColumnIDTokenUserinfoAssertion.identifier(),
AppOIDCConfigColumnClockSkew.identifier(), AppOIDCConfigColumnClockSkew.identifier(),
AppOIDCConfigColumnAdditionalOrigins.identifier(), AppOIDCConfigColumnAdditionalOrigins.identifier(),
AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(), AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(),
AppSAMLConfigColumnAppID.identifier(), AppSAMLConfigColumnAppID.identifier(),
AppSAMLConfigColumnEntityID.identifier(), AppSAMLConfigColumnEntityID.identifier(),
AppSAMLConfigColumnMetadata.identifier(), AppSAMLConfigColumnMetadata.identifier(),
AppSAMLConfigColumnMetadataURL.identifier(), AppSAMLConfigColumnMetadataURL.identifier(),
).From(appsTable.identifier()). ).From(appsTable.identifier()).
PlaceholderFormat(sq.Dollar)
if activeOnly {
return query.
LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)).
LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID)).
LeftJoin(join(AppSAMLConfigColumnAppID, AppColumnID)).
LeftJoin(join(ProjectColumnID, AppColumnProjectID)).
LeftJoin(join(OrgColumnID, AppColumnResourceOwner) + db.Timetravel(call.Took(ctx))),
scanApp
}
return query.
LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)). LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)).
LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID)). LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID)).
LeftJoin(join(AppSAMLConfigColumnAppID, AppColumnID) + db.Timetravel(call.Took(ctx))). LeftJoin(join(AppSAMLConfigColumnAppID, AppColumnID) + db.Timetravel(call.Took(ctx))),
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*App, error) { scanApp
app := new(App) }
var ( func scanApp(row *sql.Row) (*App, error) {
apiConfig = sqlAPIConfig{} app := new(App)
oidcConfig = sqlOIDCConfig{}
samlConfig = sqlSAMLConfig{}
)
err := row.Scan( var (
&app.ID, apiConfig = sqlAPIConfig{}
&app.Name, oidcConfig = sqlOIDCConfig{}
&app.ProjectID, samlConfig = sqlSAMLConfig{}
&app.CreationDate, )
&app.ChangeDate,
&app.ResourceOwner,
&app.State,
&app.Sequence,
&apiConfig.appID, err := row.Scan(
&apiConfig.clientID, &app.ID,
&apiConfig.authMethod, &app.Name,
&app.ProjectID,
&app.CreationDate,
&app.ChangeDate,
&app.ResourceOwner,
&app.State,
&app.Sequence,
&oidcConfig.appID, &apiConfig.appID,
&oidcConfig.version, &apiConfig.clientID,
&oidcConfig.clientID, &apiConfig.authMethod,
&oidcConfig.redirectUris,
&oidcConfig.responseTypes,
&oidcConfig.grantTypes,
&oidcConfig.applicationType,
&oidcConfig.authMethodType,
&oidcConfig.postLogoutRedirectUris,
&oidcConfig.devMode,
&oidcConfig.accessTokenType,
&oidcConfig.accessTokenRoleAssertion,
&oidcConfig.iDTokenRoleAssertion,
&oidcConfig.iDTokenUserinfoAssertion,
&oidcConfig.clockSkew,
&oidcConfig.additionalOrigins,
&oidcConfig.skipNativeAppSuccessPage,
&samlConfig.appID, &oidcConfig.appID,
&samlConfig.entityID, &oidcConfig.version,
&samlConfig.metadata, &oidcConfig.clientID,
&samlConfig.metadataURL, &oidcConfig.redirectUris,
) &oidcConfig.responseTypes,
&oidcConfig.grantTypes,
&oidcConfig.applicationType,
&oidcConfig.authMethodType,
&oidcConfig.postLogoutRedirectUris,
&oidcConfig.devMode,
&oidcConfig.accessTokenType,
&oidcConfig.accessTokenRoleAssertion,
&oidcConfig.iDTokenRoleAssertion,
&oidcConfig.iDTokenUserinfoAssertion,
&oidcConfig.clockSkew,
&oidcConfig.additionalOrigins,
&oidcConfig.skipNativeAppSuccessPage,
if err != nil { &samlConfig.appID,
if errors.Is(err, sql.ErrNoRows) { &samlConfig.entityID,
return nil, zerrors.ThrowNotFound(err, "QUERY-pCP8P", "Errors.App.NotExisting") &samlConfig.metadata,
} &samlConfig.metadataURL,
return nil, zerrors.ThrowInternal(err, "QUERY-4SJlx", "Errors.Internal") )
}
apiConfig.set(app) if err != nil {
oidcConfig.set(app) if errors.Is(err, sql.ErrNoRows) {
samlConfig.set(app) return nil, zerrors.ThrowNotFound(err, "QUERY-pCP8P", "Errors.App.NotExisting")
return app, nil
} }
return nil, zerrors.ThrowInternal(err, "QUERY-4SJlx", "Errors.Internal")
}
apiConfig.set(app)
oidcConfig.set(app)
samlConfig.set(app)
return app, nil
} }
func prepareOIDCAppQuery() (sq.SelectBuilder, func(*sql.Row) (*App, error)) { func prepareOIDCAppQuery() (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
@ -690,6 +717,8 @@ func prepareSAMLAppQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuil
AppSAMLConfigColumnMetadataURL.identifier(), AppSAMLConfigColumnMetadataURL.identifier(),
).From(appsTable.identifier()). ).From(appsTable.identifier()).
Join(join(AppSAMLConfigColumnAppID, AppColumnID)). Join(join(AppSAMLConfigColumnAppID, AppColumnID)).
Join(join(ProjectColumnID, AppColumnProjectID)).
Join(join(OrgColumnID, AppColumnResourceOwner)).
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*App, error) { PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*App, error) {
app := new(App) app := new(App)

View File

@ -1,6 +1,7 @@
package query package query
import ( import (
"context"
"database/sql" "database/sql"
"database/sql/driver" "database/sql/driver"
"errors" "errors"
@ -9,13 +10,15 @@ import (
"testing" "testing"
"time" "time"
sq "github.com/Masterminds/squirrel"
"github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
var ( var (
expectedAppQuery = regexp.QuoteMeta(`SELECT projections.apps7.id,` + expectedAppQueryBase = `SELECT projections.apps7.id,` +
` projections.apps7.name,` + ` projections.apps7.name,` +
` projections.apps7.project_id,` + ` projections.apps7.project_id,` +
` projections.apps7.creation_date,` + ` projections.apps7.creation_date,` +
@ -53,8 +56,11 @@ var (
` FROM projections.apps7` + ` FROM projections.apps7` +
` LEFT JOIN projections.apps7_api_configs ON projections.apps7.id = projections.apps7_api_configs.app_id AND projections.apps7.instance_id = projections.apps7_api_configs.instance_id` + ` LEFT JOIN projections.apps7_api_configs ON projections.apps7.id = projections.apps7_api_configs.app_id AND projections.apps7.instance_id = projections.apps7_api_configs.instance_id` +
` LEFT JOIN projections.apps7_oidc_configs ON projections.apps7.id = projections.apps7_oidc_configs.app_id AND projections.apps7.instance_id = projections.apps7_oidc_configs.instance_id` + ` LEFT JOIN projections.apps7_oidc_configs ON projections.apps7.id = projections.apps7_oidc_configs.app_id AND projections.apps7.instance_id = projections.apps7_oidc_configs.instance_id` +
` LEFT JOIN projections.apps7_saml_configs ON projections.apps7.id = projections.apps7_saml_configs.app_id AND projections.apps7.instance_id = projections.apps7_saml_configs.instance_id` + ` LEFT JOIN projections.apps7_saml_configs ON projections.apps7.id = projections.apps7_saml_configs.app_id AND projections.apps7.instance_id = projections.apps7_saml_configs.instance_id`
` AS OF SYSTEM TIME '-1 ms'`) expectedAppQuery = regexp.QuoteMeta(expectedAppQueryBase)
expectedActiveAppQuery = regexp.QuoteMeta(expectedAppQueryBase +
` LEFT JOIN projections.projects4 ON projections.apps7.project_id = projections.projects4.id AND projections.apps7.instance_id = projections.projects4.instance_id` +
` LEFT JOIN projections.orgs1 ON projections.apps7.resource_owner = projections.orgs1.id AND projections.apps7.instance_id = projections.orgs1.instance_id`)
expectedAppsQuery = regexp.QuoteMeta(`SELECT projections.apps7.id,` + expectedAppsQuery = regexp.QuoteMeta(`SELECT projections.apps7.id,` +
` projections.apps7.name,` + ` projections.apps7.name,` +
` projections.apps7.project_id,` + ` projections.apps7.project_id,` +
@ -1140,8 +1146,10 @@ func Test_AppPrepare(t *testing.T) {
object interface{} object interface{}
}{ }{
{ {
name: "prepareAppQuery no result", name: "prepareAppQuery no result",
prepare: prepareAppQuery, prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{ want: want{
sqlExpectations: mockQueriesScanErr( sqlExpectations: mockQueriesScanErr(
expectedAppQuery, expectedAppQuery,
@ -1158,8 +1166,10 @@ func Test_AppPrepare(t *testing.T) {
object: (*App)(nil), object: (*App)(nil),
}, },
{ {
name: "prepareAppQuery found", name: "prepareAppQuery found",
prepare: prepareAppQuery, prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{ want: want{
sqlExpectations: mockQuery( sqlExpectations: mockQuery(
expectedAppQuery, expectedAppQuery,
@ -1215,8 +1225,10 @@ func Test_AppPrepare(t *testing.T) {
}, },
}, },
{ {
name: "prepareAppQuery api app", name: "prepareAppQuery api app",
prepare: prepareAppQuery, prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{ want: want{
sqlExpectations: mockQueries( sqlExpectations: mockQueries(
expectedAppQuery, expectedAppQuery,
@ -1278,8 +1290,10 @@ func Test_AppPrepare(t *testing.T) {
}, },
}, },
{ {
name: "prepareAppQuery oidc app", name: "prepareAppQuery oidc app",
prepare: prepareAppQuery, prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{ want: want{
sqlExpectations: mockQueries( sqlExpectations: mockQueries(
expectedAppQuery, expectedAppQuery,
@ -1355,9 +1369,93 @@ func Test_AppPrepare(t *testing.T) {
SkipNativeAppSuccessPage: false, SkipNativeAppSuccessPage: false,
}, },
}, },
}, { },
name: "prepareAppQuery saml app", {
prepare: prepareAppQuery, name: "prepareAppQuery oidc app active only",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, true)
},
want: want{
sqlExpectations: mockQueries(
expectedActiveAppQuery,
appCols,
[][]driver.Value{
{
"app-id",
"app-name",
"project-id",
testNow,
testNow,
"ro",
domain.AppStateActive,
uint64(20211109),
// api config
nil,
nil,
nil,
// oidc config
"app-id",
domain.OIDCVersionV1,
"oidc-client-id",
database.TextArray[string]{"https://redirect.to/me"},
database.NumberArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken},
database.NumberArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit},
domain.OIDCApplicationTypeUserAgent,
domain.OIDCAuthMethodTypeNone,
database.TextArray[string]{"post.logout.ch"},
true,
domain.OIDCTokenTypeJWT,
true,
true,
true,
1 * time.Second,
database.TextArray[string]{"additional.origin"},
false,
// saml config
nil,
nil,
nil,
nil,
},
},
),
},
object: &App{
ID: "app-id",
CreationDate: testNow,
ChangeDate: testNow,
ResourceOwner: "ro",
State: domain.AppStateActive,
Sequence: 20211109,
Name: "app-name",
ProjectID: "project-id",
OIDCConfig: &OIDCApp{
Version: domain.OIDCVersionV1,
ClientID: "oidc-client-id",
RedirectURIs: database.TextArray[string]{"https://redirect.to/me"},
ResponseTypes: database.NumberArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken},
GrantTypes: database.NumberArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit},
AppType: domain.OIDCApplicationTypeUserAgent,
AuthMethodType: domain.OIDCAuthMethodTypeNone,
PostLogoutRedirectURIs: database.TextArray[string]{"post.logout.ch"},
IsDevMode: true,
AccessTokenType: domain.OIDCTokenTypeJWT,
AssertAccessTokenRole: true,
AssertIDTokenRole: true,
AssertIDTokenUserinfo: true,
ClockSkew: 1 * time.Second,
AdditionalOrigins: database.TextArray[string]{"additional.origin"},
ComplianceProblems: nil,
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
},
},
},
{
name: "prepareAppQuery saml app",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{ want: want{
sqlExpectations: mockQueries( sqlExpectations: mockQueries(
expectedAppQuery, expectedAppQuery,
@ -1420,8 +1518,10 @@ func Test_AppPrepare(t *testing.T) {
}, },
}, },
{ {
name: "prepareAppQuery oidc app IsDevMode inactive", name: "prepareAppQuery oidc app IsDevMode inactive",
prepare: prepareAppQuery, prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{ want: want{
sqlExpectations: mockQueries( sqlExpectations: mockQueries(
expectedAppQuery, expectedAppQuery,
@ -1499,8 +1599,10 @@ func Test_AppPrepare(t *testing.T) {
}, },
}, },
{ {
name: "prepareAppQuery oidc app AssertAccessTokenRole inactive", name: "prepareAppQuery oidc app AssertAccessTokenRole inactive",
prepare: prepareAppQuery, prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{ want: want{
sqlExpectations: mockQueries( sqlExpectations: mockQueries(
expectedAppQuery, expectedAppQuery,
@ -1578,8 +1680,10 @@ func Test_AppPrepare(t *testing.T) {
}, },
}, },
{ {
name: "prepareAppQuery oidc app AssertIDTokenRole inactive", name: "prepareAppQuery oidc app AssertIDTokenRole inactive",
prepare: prepareAppQuery, prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{ want: want{
sqlExpectations: mockQueries( sqlExpectations: mockQueries(
expectedAppQuery, expectedAppQuery,
@ -1657,8 +1761,10 @@ func Test_AppPrepare(t *testing.T) {
}, },
}, },
{ {
name: "prepareAppQuery oidc app AssertIDTokenUserinfo inactive", name: "prepareAppQuery oidc app AssertIDTokenUserinfo inactive",
prepare: prepareAppQuery, prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{ want: want{
sqlExpectations: mockQueries( sqlExpectations: mockQueries(
expectedAppQuery, expectedAppQuery,
@ -1736,8 +1842,10 @@ func Test_AppPrepare(t *testing.T) {
}, },
}, },
{ {
name: "prepareAppQuery sql err", name: "prepareAppQuery sql err",
prepare: prepareAppQuery, prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{ want: want{
sqlExpectations: mockQueryErr( sqlExpectations: mockQueryErr(
expectedAppQuery, expectedAppQuery,

View File

@ -10,7 +10,6 @@ import (
"time" "time"
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
"github.com/shopspring/decimal"
"github.com/zitadel/logging" "github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/call" "github.com/zitadel/zitadel/internal/api/call"
@ -27,7 +26,7 @@ type Stateful interface {
type State struct { type State struct {
LastRun time.Time LastRun time.Time
Position decimal.Decimal Position float64
EventCreatedAt time.Time EventCreatedAt time.Time
AggregateID string AggregateID string
AggregateType eventstore.AggregateType AggregateType eventstore.AggregateType
@ -222,7 +221,7 @@ func prepareLatestState(ctx context.Context, db prepareDatabase) (sq.SelectBuild
var ( var (
creationDate sql.NullTime creationDate sql.NullTime
lastUpdated sql.NullTime lastUpdated sql.NullTime
position decimal.NullDecimal position sql.NullFloat64
) )
err := row.Scan( err := row.Scan(
&creationDate, &creationDate,
@ -235,7 +234,7 @@ func prepareLatestState(ctx context.Context, db prepareDatabase) (sq.SelectBuild
return &State{ return &State{
EventCreatedAt: creationDate.Time, EventCreatedAt: creationDate.Time,
LastRun: lastUpdated.Time, LastRun: lastUpdated.Time,
Position: position.Decimal, Position: position.Float64,
}, nil }, nil
} }
} }
@ -260,7 +259,7 @@ func prepareCurrentStateQuery(ctx context.Context, db prepareDatabase) (sq.Selec
var ( var (
lastRun sql.NullTime lastRun sql.NullTime
eventDate sql.NullTime eventDate sql.NullTime
currentPosition decimal.NullDecimal currentPosition sql.NullFloat64
aggregateType sql.NullString aggregateType sql.NullString
aggregateID sql.NullString aggregateID sql.NullString
sequence sql.NullInt64 sequence sql.NullInt64
@ -281,7 +280,7 @@ func prepareCurrentStateQuery(ctx context.Context, db prepareDatabase) (sq.Selec
} }
currentState.State.EventCreatedAt = eventDate.Time currentState.State.EventCreatedAt = eventDate.Time
currentState.State.LastRun = lastRun.Time currentState.State.LastRun = lastRun.Time
currentState.Position = currentPosition.Decimal currentState.Position = currentPosition.Float64
currentState.AggregateType = eventstore.AggregateType(aggregateType.String) currentState.AggregateType = eventstore.AggregateType(aggregateType.String)
currentState.AggregateID = aggregateID.String currentState.AggregateID = aggregateID.String
currentState.Sequence = uint64(sequence.Int64) currentState.Sequence = uint64(sequence.Int64)

View File

@ -7,8 +7,6 @@ import (
"fmt" "fmt"
"regexp" "regexp"
"testing" "testing"
"github.com/shopspring/decimal"
) )
var ( var (
@ -89,7 +87,7 @@ func Test_CurrentSequencesPrepares(t *testing.T) {
State: State{ State: State{
EventCreatedAt: testNow, EventCreatedAt: testNow,
LastRun: testNow, LastRun: testNow,
Position: decimal.NewFromInt(20211108), Position: 20211108,
AggregateID: "agg-id", AggregateID: "agg-id",
AggregateType: "agg-type", AggregateType: "agg-type",
Sequence: 20211108, Sequence: 20211108,
@ -136,7 +134,7 @@ func Test_CurrentSequencesPrepares(t *testing.T) {
ProjectionName: "projection-name", ProjectionName: "projection-name",
State: State{ State: State{
EventCreatedAt: testNow, EventCreatedAt: testNow,
Position: decimal.NewFromInt(20211108), Position: 20211108,
LastRun: testNow, LastRun: testNow,
AggregateID: "agg-id", AggregateID: "agg-id",
AggregateType: "agg-type", AggregateType: "agg-type",
@ -147,7 +145,7 @@ func Test_CurrentSequencesPrepares(t *testing.T) {
ProjectionName: "projection-name2", ProjectionName: "projection-name2",
State: State{ State: State{
EventCreatedAt: testNow, EventCreatedAt: testNow,
Position: decimal.NewFromInt(20211108), Position: 20211108,
LastRun: testNow, LastRun: testNow,
AggregateID: "agg-id", AggregateID: "agg-id",
AggregateType: "agg-type", AggregateType: "agg-type",

View File

@ -52,7 +52,7 @@ type IntrospectionClient struct {
//go:embed introspection_client_by_id.sql //go:embed introspection_client_by_id.sql
var introspectionClientByIDQuery string var introspectionClientByIDQuery string
func (q *Queries) GetIntrospectionClientByID(ctx context.Context, clientID string, getKeys bool) (_ *IntrospectionClient, err error) { func (q *Queries) ActiveIntrospectionClientByID(ctx context.Context, clientID string, getKeys bool) (_ *IntrospectionClient, err error) {
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()

View File

@ -20,6 +20,7 @@ keys as (
) )
select config.app_id, config.client_id, config.client_secret, config.app_type, apps.project_id, apps.resource_owner, p.project_role_assertion, keys.public_keys select config.app_id, config.client_id, config.client_secret, config.app_type, apps.project_id, apps.resource_owner, p.project_role_assertion, keys.public_keys
from config from config
join projections.apps7 apps on apps.id = config.app_id and apps.instance_id = config.instance_id join projections.apps7 apps on apps.id = config.app_id and apps.instance_id = config.instance_id and apps.state = 1
join projections.projects4 p on p.id = apps.project_id and p.instance_id = $1 join projections.projects4 p on p.id = apps.project_id and p.instance_id = $1 and p.state = 1
join projections.orgs1 o on o.id = p.resource_owner and o.instance_id = config.instance_id and o.org_state = 1
left join keys on keys.client_id = config.client_id; left join keys on keys.client_id = config.client_id;

View File

@ -14,7 +14,7 @@ import (
"github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/database"
) )
func TestQueries_GetIntrospectionClientByID(t *testing.T) { func TestQueries_ActiveIntrospectionClientByID(t *testing.T) {
pubkeys := database.Map[[]byte]{ pubkeys := database.Map[[]byte]{
"key1": {1, 2, 3}, "key1": {1, 2, 3},
"key2": {4, 5, 6}, "key2": {4, 5, 6},
@ -96,7 +96,7 @@ func TestQueries_GetIntrospectionClientByID(t *testing.T) {
}, },
} }
ctx := authz.NewMockContext("instanceID", "orgID", "userID") ctx := authz.NewMockContext("instanceID", "orgID", "userID")
got, err := q.GetIntrospectionClientByID(ctx, tt.args.clientID, tt.args.getKeys) got, err := q.ActiveIntrospectionClientByID(ctx, tt.args.clientID, tt.args.getKeys)
require.ErrorIs(t, err, tt.wantErr) require.ErrorIs(t, err, tt.wantErr)
assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, got)
}) })

View File

@ -45,7 +45,7 @@ type OIDCClient struct {
//go:embed oidc_client_by_id.sql //go:embed oidc_client_by_id.sql
var oidcClientQuery string var oidcClientQuery string
func (q *Queries) GetOIDCClientByID(ctx context.Context, clientID string, getKeys bool) (client *OIDCClient, err error) { func (q *Queries) ActiveOIDCClientByID(ctx context.Context, clientID string, getKeys bool) (client *OIDCClient, err error) {
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()

View File

@ -5,8 +5,9 @@ with client as (
c.access_token_type, c.access_token_role_assertion, c.id_token_role_assertion, c.access_token_type, c.access_token_role_assertion, c.id_token_role_assertion,
c.id_token_userinfo_assertion, c.clock_skew, c.additional_origins, a.project_id, p.project_role_assertion c.id_token_userinfo_assertion, c.clock_skew, c.additional_origins, a.project_id, p.project_role_assertion
from projections.apps7_oidc_configs c from projections.apps7_oidc_configs c
join projections.apps7 a on a.id = c.app_id and a.instance_id = c.instance_id join projections.apps7 a on a.id = c.app_id and a.instance_id = c.instance_id and a.state = 1
join projections.projects4 p on p.id = a.project_id and p.instance_id = a.instance_id join projections.projects4 p on p.id = a.project_id and p.instance_id = a.instance_id and p.state = 1
join projections.orgs1 o on o.id = p.resource_owner and o.instance_id = c.instance_id and o.org_state = 1
where c.instance_id = $1 where c.instance_id = $1
and c.client_id = $2 and c.client_id = $2
), ),

View File

@ -29,7 +29,7 @@ var (
testdataOidcClientNoSettings string testdataOidcClientNoSettings string
) )
func TestQueries_GetOIDCClientByID(t *testing.T) { func TestQueries_ActiveOIDCClientByID(t *testing.T) {
expQuery := regexp.QuoteMeta(oidcClientQuery) expQuery := regexp.QuoteMeta(oidcClientQuery)
cols := []string{"client"} cols := []string{"client"}
pubkey := `-----BEGIN RSA PUBLIC KEY----- pubkey := `-----BEGIN RSA PUBLIC KEY-----
@ -232,7 +232,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx
}, },
} }
ctx := authz.NewMockContext("instanceID", "orgID", "loginClient") ctx := authz.NewMockContext("instanceID", "orgID", "loginClient")
got, err := q.GetOIDCClientByID(ctx, "clientID", true) got, err := q.ActiveOIDCClientByID(ctx, "clientID", true)
require.ErrorIs(t, err, tt.wantErr) require.ErrorIs(t, err, tt.wantErr)
assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, got)
}) })

View File

@ -11,7 +11,7 @@ import (
) )
const ( const (
SMTPConfigProjectionTable = "projections.smtp_configs3" SMTPConfigProjectionTable = "projections.smtp_configs4"
SMTPConfigTable = SMTPConfigProjectionTable + "_" + smtpConfigSMTPTableSuffix SMTPConfigTable = SMTPConfigProjectionTable + "_" + smtpConfigSMTPTableSuffix
SMTPConfigHTTPTable = SMTPConfigProjectionTable + "_" + smtpConfigHTTPTableSuffix SMTPConfigHTTPTable = SMTPConfigProjectionTable + "_" + smtpConfigHTTPTableSuffix
@ -174,7 +174,7 @@ func (p *smtpConfigProjection) reduceSMTPConfigAdded(event eventstore.Event) (*h
handler.AddCreateStatement( handler.AddCreateStatement(
[]handler.Column{ []handler.Column{
handler.NewCol(SMTPConfigSMTPColumnInstanceID, e.Aggregate().InstanceID), handler.NewCol(SMTPConfigSMTPColumnInstanceID, e.Aggregate().InstanceID),
handler.NewCol(SMTPConfigSMTPColumnID, e.ID), handler.NewCol(SMTPConfigSMTPColumnID, id),
handler.NewCol(SMTPConfigSMTPColumnTLS, e.TLS), handler.NewCol(SMTPConfigSMTPColumnTLS, e.TLS),
handler.NewCol(SMTPConfigSMTPColumnSenderAddress, e.SenderAddress), handler.NewCol(SMTPConfigSMTPColumnSenderAddress, e.SenderAddress),
handler.NewCol(SMTPConfigSMTPColumnSenderName, e.SenderName), handler.NewCol(SMTPConfigSMTPColumnSenderName, e.SenderName),

View File

@ -50,7 +50,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -60,7 +60,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.smtp_configs3_smtp SET (tls, sender_address, sender_name, reply_to_address, host, username) = ($1, $2, $3, $4, $5, $6) WHERE (id = $7) AND (instance_id = $8)", expectedStmt: "UPDATE projections.smtp_configs4_smtp SET (tls, sender_address, sender_name, reply_to_address, host, username) = ($1, $2, $3, $4, $5, $6) WHERE (id = $7) AND (instance_id = $8)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
true, true,
"sender", "sender",
@ -100,7 +100,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -137,7 +137,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -146,7 +146,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.smtp_configs3_smtp SET sender_address = $1 WHERE (id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.smtp_configs4_smtp SET sender_address = $1 WHERE (id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"sender", "sender",
"config-id", "config-id",
@ -182,7 +182,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -192,7 +192,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.smtp_configs3_http SET endpoint = $1 WHERE (id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.smtp_configs4_http SET endpoint = $1 WHERE (id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"endpoint", "endpoint",
"config-id", "config-id",
@ -227,7 +227,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -264,7 +264,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -273,7 +273,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.smtp_configs3_http SET endpoint = $1 WHERE (id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.smtp_configs4_http SET endpoint = $1 WHERE (id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"endpoint", "endpoint",
"config-id", "config-id",
@ -284,6 +284,69 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
}, },
}, },
}, },
{
name: "reduceSMTPConfigAdded (no id)",
args: args{
event: getEvent(
testEvent(
instance.SMTPConfigAddedEventType,
instance.AggregateType,
[]byte(`{
"instance_id": "instance-id",
"resource_owner": "ro-id",
"aggregate_id": "agg-id",
"tls": true,
"senderAddress": "sender",
"senderName": "name",
"replyToAddress": "reply-to",
"host": "host",
"user": "user",
"password": {
"cryptoType": 0,
"algorithm": "RSA-265",
"keyId": "key-id"
}
}`),
), eventstore.GenericEventMapper[instance.SMTPConfigAddedEvent]),
},
reduce: (&smtpConfigProjection{}).reduceSMTPConfigAdded,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.smtp_configs4 (creation_date, change_date, instance_id, resource_owner, aggregate_id, id, sequence, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{
anyArg{},
anyArg{},
"instance-id",
"ro-id",
"agg-id",
"ro-id",
uint64(15),
domain.SMTPConfigStateActive,
"generic",
},
},
{
expectedStmt: "INSERT INTO projections.smtp_configs4_smtp (instance_id, id, tls, sender_address, sender_name, reply_to_address, host, username, password) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{
"instance-id",
"ro-id",
true,
"sender",
"name",
"reply-to",
"host",
"user",
anyArg{},
},
},
},
},
},
},
{ {
name: "reduceSMTPConfigAdded", name: "reduceSMTPConfigAdded",
args: args{ args: args{
@ -318,7 +381,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO projections.smtp_configs3 (creation_date, change_date, instance_id, resource_owner, aggregate_id, id, sequence, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedStmt: "INSERT INTO projections.smtp_configs4 (creation_date, change_date, instance_id, resource_owner, aggregate_id, id, sequence, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
anyArg{}, anyArg{},
@ -332,7 +395,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.smtp_configs3_smtp (instance_id, id, tls, sender_address, sender_name, reply_to_address, host, username, password) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedStmt: "INSERT INTO projections.smtp_configs4_smtp (instance_id, id, tls, sender_address, sender_name, reply_to_address, host, username, password) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"instance-id", "instance-id",
"config-id", "config-id",
@ -374,7 +437,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO projections.smtp_configs3 (creation_date, change_date, instance_id, resource_owner, aggregate_id, id, sequence, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedStmt: "INSERT INTO projections.smtp_configs4 (creation_date, change_date, instance_id, resource_owner, aggregate_id, id, sequence, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
anyArg{}, anyArg{},
@ -388,7 +451,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.smtp_configs3_http (instance_id, id, endpoint) VALUES ($1, $2, $3)", expectedStmt: "INSERT INTO projections.smtp_configs4_http (instance_id, id, endpoint) VALUES ($1, $2, $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"instance-id", "instance-id",
"config-id", "config-id",
@ -420,7 +483,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (NOT (id = $4)) AND (state = $5) AND (instance_id = $6)", expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (NOT (id = $4)) AND (state = $5) AND (instance_id = $6)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -431,7 +494,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -465,7 +528,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -505,7 +568,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.smtp_configs3_smtp SET password = $1 WHERE (id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.smtp_configs4_smtp SET password = $1 WHERE (id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
"config-id", "config-id",
@ -513,7 +576,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -546,7 +609,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "DELETE FROM projections.smtp_configs3 WHERE (id = $1) AND (instance_id = $2)", expectedStmt: "DELETE FROM projections.smtp_configs4 WHERE (id = $1) AND (instance_id = $2)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"config-id", "config-id",
"instance-id", "instance-id",
@ -573,7 +636,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "DELETE FROM projections.smtp_configs3 WHERE (instance_id = $1)", expectedStmt: "DELETE FROM projections.smtp_configs4 WHERE (instance_id = $1)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
}, },

View File

@ -14,26 +14,26 @@ import (
) )
var ( var (
prepareSMTPConfigStmt = `SELECT projections.smtp_configs3.creation_date,` + prepareSMTPConfigStmt = `SELECT projections.smtp_configs4.creation_date,` +
` projections.smtp_configs3.change_date,` + ` projections.smtp_configs4.change_date,` +
` projections.smtp_configs3.resource_owner,` + ` projections.smtp_configs4.resource_owner,` +
` projections.smtp_configs3.sequence,` + ` projections.smtp_configs4.sequence,` +
` projections.smtp_configs3.id,` + ` projections.smtp_configs4.id,` +
` projections.smtp_configs3.state,` + ` projections.smtp_configs4.state,` +
` projections.smtp_configs3.description,` + ` projections.smtp_configs4.description,` +
` projections.smtp_configs3_smtp.id,` + ` projections.smtp_configs4_smtp.id,` +
` projections.smtp_configs3_smtp.tls,` + ` projections.smtp_configs4_smtp.tls,` +
` projections.smtp_configs3_smtp.sender_address,` + ` projections.smtp_configs4_smtp.sender_address,` +
` projections.smtp_configs3_smtp.sender_name,` + ` projections.smtp_configs4_smtp.sender_name,` +
` projections.smtp_configs3_smtp.reply_to_address,` + ` projections.smtp_configs4_smtp.reply_to_address,` +
` projections.smtp_configs3_smtp.host,` + ` projections.smtp_configs4_smtp.host,` +
` projections.smtp_configs3_smtp.username,` + ` projections.smtp_configs4_smtp.username,` +
` projections.smtp_configs3_smtp.password,` + ` projections.smtp_configs4_smtp.password,` +
` projections.smtp_configs3_http.id,` + ` projections.smtp_configs4_http.id,` +
` projections.smtp_configs3_http.endpoint` + ` projections.smtp_configs4_http.endpoint` +
` FROM projections.smtp_configs3` + ` FROM projections.smtp_configs4` +
` LEFT JOIN projections.smtp_configs3_smtp ON projections.smtp_configs3.id = projections.smtp_configs3_smtp.id AND projections.smtp_configs3.instance_id = projections.smtp_configs3_smtp.instance_id` + ` LEFT JOIN projections.smtp_configs4_smtp ON projections.smtp_configs4.id = projections.smtp_configs4_smtp.id AND projections.smtp_configs4.instance_id = projections.smtp_configs4_smtp.instance_id` +
` LEFT JOIN projections.smtp_configs3_http ON projections.smtp_configs3.id = projections.smtp_configs3_http.id AND projections.smtp_configs3.instance_id = projections.smtp_configs3_http.instance_id` + ` LEFT JOIN projections.smtp_configs4_http ON projections.smtp_configs4.id = projections.smtp_configs4_http.id AND projections.smtp_configs4.instance_id = projections.smtp_configs4_http.instance_id` +
` AS OF SYSTEM TIME '-1 ms'` ` AS OF SYSTEM TIME '-1 ms'`
prepareSMTPConfigCols = []string{ prepareSMTPConfigCols = []string{
"creation_date", "creation_date",

View File

@ -143,6 +143,10 @@ func NewUserGrantRoleQuery(value string) (SearchQuery, error) {
return NewTextQuery(UserGrantRoles, value, TextListContains) return NewTextQuery(UserGrantRoles, value, TextListContains)
} }
func NewUserGrantStateQuery(value domain.UserGrantState) (SearchQuery, error) {
return NewNumberQuery(UserGrantState, value, NumberEquals)
}
func NewUserGrantWithGrantedQuery(owner string) (SearchQuery, error) { func NewUserGrantWithGrantedQuery(owner string) (SearchQuery, error) {
orgQuery, err := NewUserGrantResourceOwnerSearchQuery(owner) orgQuery, err := NewUserGrantResourceOwnerSearchQuery(owner)
if err != nil { if err != nil {
@ -277,7 +281,7 @@ func (q *Queries) UserGrants(ctx context.Context, queries *UserGrantsQueries, sh
return nil, zerrors.ThrowInternal(err, "QUERY-wXnQR", "Errors.Query.SQLStatement") return nil, zerrors.ThrowInternal(err, "QUERY-wXnQR", "Errors.Query.SQLStatement")
} }
latestState, err := q.latestState(ctx, userGrantTable) latestSequence, err := q.latestState(ctx, userGrantTable)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -290,7 +294,7 @@ func (q *Queries) UserGrants(ctx context.Context, queries *UserGrantsQueries, sh
return nil, err return nil, err
} }
grants.State = latestState grants.State = latestSequence
return grants, nil return grants, nil
} }

View File

@ -144,7 +144,7 @@ func (q *Queries) Memberships(ctx context.Context, queries *MembershipSearchQuer
if err != nil { if err != nil {
return nil, zerrors.ThrowInvalidArgument(err, "QUERY-T84X9", "Errors.Query.InvalidRequest") return nil, zerrors.ThrowInvalidArgument(err, "QUERY-T84X9", "Errors.Query.InvalidRequest")
} }
latestState, err := q.latestState(ctx, orgMemberTable, instanceMemberTable, projectMemberTable, projectGrantMemberTable) latestSequence, err := q.latestState(ctx, orgMemberTable, instanceMemberTable, projectMemberTable, projectGrantMemberTable)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -157,7 +157,7 @@ func (q *Queries) Memberships(ctx context.Context, queries *MembershipSearchQuer
if err != nil { if err != nil {
return nil, err return nil, err
} }
memberships.State = latestState memberships.State = latestSequence
return memberships, nil return memberships, nil
} }

View File

@ -2,7 +2,7 @@ with usr as (
select u.id, u.creation_date, u.change_date, u.sequence, u.state, u.resource_owner, u.username, n.login_name as preferred_login_name select u.id, u.creation_date, u.change_date, u.sequence, u.state, u.resource_owner, u.username, n.login_name as preferred_login_name
from projections.users13 u from projections.users13 u
left join projections.login_names3 n on u.id = n.user_id and u.instance_id = n.instance_id left join projections.login_names3 n on u.id = n.user_id and u.instance_id = n.instance_id
where u.id = $1 where u.id = $1 and u.state = 1 -- only allow active users
and u.instance_id = $2 and u.instance_id = $2
and n.is_primary = true and n.is_primary = true
), ),
@ -38,6 +38,7 @@ user_grants as (
where user_id = $1 where user_id = $1
and instance_id = $2 and instance_id = $2
and project_id = any($3) and project_id = any($3)
and state = 1
{{ if . -}} {{ if . -}}
and resource_owner = any($4) and resource_owner = any($4)
{{- end }} {{- end }}

View File

@ -3,7 +3,6 @@ package database
import ( import (
"time" "time"
"github.com/shopspring/decimal"
"github.com/zitadel/logging" "github.com/zitadel/logging"
"golang.org/x/exp/constraints" "golang.org/x/exp/constraints"
) )
@ -95,7 +94,7 @@ func (c numberCompare) String() string {
} }
type number interface { type number interface {
constraints.Integer | constraints.Float | time.Time | decimal.Decimal constraints.Integer | constraints.Float | time.Time
// TODO: condition must know if it's args are named parameters or not // TODO: condition must know if it's args are named parameters or not
// constraints.Integer | constraints.Float | time.Time | placeholder // constraints.Integer | constraints.Float | time.Time | placeholder
} }

View File

@ -2,8 +2,6 @@ package eventstore
import ( import (
"context" "context"
"github.com/shopspring/decimal"
) )
func NewEventstore(querier Querier, pusher Pusher) *EventStore { func NewEventstore(querier Querier, pusher Pusher) *EventStore {
@ -32,12 +30,12 @@ type healthier interface {
} }
type GlobalPosition struct { type GlobalPosition struct {
Position decimal.Decimal Position float64
InPositionOrder uint32 InPositionOrder uint32
} }
func (gp GlobalPosition) IsLess(other GlobalPosition) bool { func (gp GlobalPosition) IsLess(other GlobalPosition) bool {
return gp.Position.LessThan(other.Position) || (gp.Position.Equal(other.Position) && gp.InPositionOrder < other.InPositionOrder) return gp.Position < other.Position || (gp.Position == other.Position && gp.InPositionOrder < other.InPositionOrder)
} }
type Reducer interface { type Reducer interface {

View File

@ -8,8 +8,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/v2/database/mock" "github.com/zitadel/zitadel/internal/v2/database/mock"
"github.com/zitadel/zitadel/internal/v2/eventstore" "github.com/zitadel/zitadel/internal/v2/eventstore"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
@ -820,7 +818,7 @@ func Test_push(t *testing.T) {
[][]driver.Value{ [][]driver.Value{
{ {
time.Now(), time.Now(),
decimal.NewFromFloat(123).String(), float64(123),
}, },
}, },
), ),
@ -901,11 +899,11 @@ func Test_push(t *testing.T) {
[][]driver.Value{ [][]driver.Value{
{ {
time.Now(), time.Now(),
decimal.NewFromFloat(123).String(), float64(123),
}, },
{ {
time.Now(), time.Now(),
decimal.NewFromFloat(123.1).String(), float64(123.1),
}, },
}, },
), ),
@ -986,11 +984,11 @@ func Test_push(t *testing.T) {
[][]driver.Value{ [][]driver.Value{
{ {
time.Now(), time.Now(),
decimal.NewFromFloat(123).String(), float64(123),
}, },
{ {
time.Now(), time.Now(),
decimal.NewFromFloat(123.1).String(), float64(123.1),
}, },
}, },
), ),
@ -1046,7 +1044,7 @@ func Test_push(t *testing.T) {
[][]driver.Value{ [][]driver.Value{
{ {
time.Now(), time.Now(),
decimal.NewFromFloat(123).String(), float64(123),
}, },
}, },
), ),
@ -1101,7 +1099,7 @@ func Test_push(t *testing.T) {
[][]driver.Value{ [][]driver.Value{
{ {
time.Now(), time.Now(),
decimal.NewFromFloat(123).String(), float64(123),
}, },
}, },
), ),
@ -1183,11 +1181,11 @@ func Test_push(t *testing.T) {
[][]driver.Value{ [][]driver.Value{
{ {
time.Now(), time.Now(),
decimal.NewFromFloat(123).String(), float64(123),
}, },
{ {
time.Now(), time.Now(),
decimal.NewFromFloat(123.1).String(), float64(123.1),
}, },
}, },
), ),
@ -1274,11 +1272,11 @@ func Test_push(t *testing.T) {
[][]driver.Value{ [][]driver.Value{
{ {
time.Now(), time.Now(),
decimal.NewFromFloat(123).String(), float64(123),
}, },
{ {
time.Now(), time.Now(),
decimal.NewFromFloat(123.1).String(), float64(123.1),
}, },
}, },
), ),

View File

@ -8,8 +8,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/v2/database" "github.com/zitadel/zitadel/internal/v2/database"
"github.com/zitadel/zitadel/internal/v2/database/mock" "github.com/zitadel/zitadel/internal/v2/database/mock"
"github.com/zitadel/zitadel/internal/v2/eventstore" "github.com/zitadel/zitadel/internal/v2/eventstore"
@ -543,13 +541,13 @@ func Test_writeFilter(t *testing.T) {
args: args{ args: args{
filter: eventstore.NewFilter( filter: eventstore.NewFilter(
eventstore.FilterPagination( eventstore.FilterPagination(
eventstore.PositionGreater(decimal.NewFromFloat(123.4), 0), eventstore.PositionGreater(123.4, 0),
), ),
), ),
}, },
want: wantQuery{ want: wantQuery{
query: " WHERE instance_id = $1 AND position > $2 ORDER BY position, in_tx_order", query: " WHERE instance_id = $1 AND position > $2 ORDER BY position, in_tx_order",
args: []any{"i1", decimal.NewFromFloat(123.4)}, args: []any{"i1", 123.4},
}, },
}, },
{ {
@ -557,18 +555,18 @@ func Test_writeFilter(t *testing.T) {
args: args{ args: args{
filter: eventstore.NewFilter( filter: eventstore.NewFilter(
eventstore.FilterPagination( eventstore.FilterPagination(
// eventstore.PositionGreater(decimal.NewFromFloat(123.4), 0), // eventstore.PositionGreater(123.4, 0),
// eventstore.PositionLess(125.4, 10), // eventstore.PositionLess(125.4, 10),
eventstore.PositionBetween( eventstore.PositionBetween(
&eventstore.GlobalPosition{Position: decimal.NewFromFloat(123.4)}, &eventstore.GlobalPosition{Position: 123.4},
&eventstore.GlobalPosition{Position: decimal.NewFromFloat(125.4), InPositionOrder: 10}, &eventstore.GlobalPosition{Position: 125.4, InPositionOrder: 10},
), ),
), ),
), ),
}, },
want: wantQuery{ want: wantQuery{
query: " WHERE instance_id = $1 AND ((position = $2 AND in_tx_order < $3) OR position < $4) AND position > $5 ORDER BY position, in_tx_order", query: " WHERE instance_id = $1 AND ((position = $2 AND in_tx_order < $3) OR position < $4) AND position > $5 ORDER BY position, in_tx_order",
args: []any{"i1", decimal.NewFromFloat(125.4), uint32(10), decimal.NewFromFloat(125.4), decimal.NewFromFloat(123.4)}, args: []any{"i1", 125.4, uint32(10), 125.4, 123.4},
// TODO: (adlerhurst) would require some refactoring to reuse existing args // TODO: (adlerhurst) would require some refactoring to reuse existing args
// query: " WHERE instance_id = $1 AND position > $2 AND ((position = $3 AND in_tx_order < $4) OR position < $3) ORDER BY position, in_tx_order", // query: " WHERE instance_id = $1 AND position > $2 AND ((position = $3 AND in_tx_order < $4) OR position < $3) ORDER BY position, in_tx_order",
// args: []any{"i1", 123.4, 125.4, uint32(10)}, // args: []any{"i1", 123.4, 125.4, uint32(10)},
@ -579,13 +577,13 @@ func Test_writeFilter(t *testing.T) {
args: args{ args: args{
filter: eventstore.NewFilter( filter: eventstore.NewFilter(
eventstore.FilterPagination( eventstore.FilterPagination(
eventstore.PositionGreater(decimal.NewFromFloat(123.4), 12), eventstore.PositionGreater(123.4, 12),
), ),
), ),
}, },
want: wantQuery{ want: wantQuery{
query: " WHERE instance_id = $1 AND ((position = $2 AND in_tx_order > $3) OR position > $4) ORDER BY position, in_tx_order", query: " WHERE instance_id = $1 AND ((position = $2 AND in_tx_order > $3) OR position > $4) ORDER BY position, in_tx_order",
args: []any{"i1", decimal.NewFromFloat(123.4), uint32(12), decimal.NewFromFloat(123.4)}, args: []any{"i1", 123.4, uint32(12), 123.4},
}, },
}, },
{ {
@ -595,13 +593,13 @@ func Test_writeFilter(t *testing.T) {
eventstore.FilterPagination( eventstore.FilterPagination(
eventstore.Limit(10), eventstore.Limit(10),
eventstore.Offset(3), eventstore.Offset(3),
eventstore.PositionGreater(decimal.NewFromFloat(123.4), 12), eventstore.PositionGreater(123.4, 12),
), ),
), ),
}, },
want: wantQuery{ want: wantQuery{
query: " WHERE instance_id = $1 AND ((position = $2 AND in_tx_order > $3) OR position > $4) ORDER BY position, in_tx_order LIMIT $5 OFFSET $6", query: " WHERE instance_id = $1 AND ((position = $2 AND in_tx_order > $3) OR position > $4) ORDER BY position, in_tx_order LIMIT $5 OFFSET $6",
args: []any{"i1", decimal.NewFromFloat(123.4), uint32(12), decimal.NewFromFloat(123.4), uint32(10), uint32(3)}, args: []any{"i1", 123.4, uint32(12), 123.4, uint32(10), uint32(3)},
}, },
}, },
{ {
@ -611,14 +609,14 @@ func Test_writeFilter(t *testing.T) {
eventstore.FilterPagination( eventstore.FilterPagination(
eventstore.Limit(10), eventstore.Limit(10),
eventstore.Offset(3), eventstore.Offset(3),
eventstore.PositionGreater(decimal.NewFromFloat(123.4), 12), eventstore.PositionGreater(123.4, 12),
), ),
eventstore.AppendAggregateFilter("user"), eventstore.AppendAggregateFilter("user"),
), ),
}, },
want: wantQuery{ want: wantQuery{
query: " WHERE instance_id = $1 AND aggregate_type = $2 AND ((position = $3 AND in_tx_order > $4) OR position > $5) ORDER BY position, in_tx_order LIMIT $6 OFFSET $7", query: " WHERE instance_id = $1 AND aggregate_type = $2 AND ((position = $3 AND in_tx_order > $4) OR position > $5) ORDER BY position, in_tx_order LIMIT $6 OFFSET $7",
args: []any{"i1", "user", decimal.NewFromFloat(123.4), uint32(12), decimal.NewFromFloat(123.4), uint32(10), uint32(3)}, args: []any{"i1", "user", 123.4, uint32(12), 123.4, uint32(10), uint32(3)},
}, },
}, },
{ {
@ -628,7 +626,7 @@ func Test_writeFilter(t *testing.T) {
eventstore.FilterPagination( eventstore.FilterPagination(
eventstore.Limit(10), eventstore.Limit(10),
eventstore.Offset(3), eventstore.Offset(3),
eventstore.PositionGreater(decimal.NewFromFloat(123.4), 12), eventstore.PositionGreater(123.4, 12),
), ),
eventstore.AppendAggregateFilter("user"), eventstore.AppendAggregateFilter("user"),
eventstore.AppendAggregateFilter( eventstore.AppendAggregateFilter(
@ -639,7 +637,7 @@ func Test_writeFilter(t *testing.T) {
}, },
want: wantQuery{ want: wantQuery{
query: " WHERE instance_id = $1 AND (aggregate_type = $2 OR (aggregate_type = $3 AND aggregate_id = $4)) AND ((position = $5 AND in_tx_order > $6) OR position > $7) ORDER BY position, in_tx_order LIMIT $8 OFFSET $9", query: " WHERE instance_id = $1 AND (aggregate_type = $2 OR (aggregate_type = $3 AND aggregate_id = $4)) AND ((position = $5 AND in_tx_order > $6) OR position > $7) ORDER BY position, in_tx_order LIMIT $8 OFFSET $9",
args: []any{"i1", "user", "org", "o1", decimal.NewFromFloat(123.4), uint32(12), decimal.NewFromFloat(123.4), uint32(10), uint32(3)}, args: []any{"i1", "user", "org", "o1", 123.4, uint32(12), 123.4, uint32(10), uint32(3)},
}, },
}, },
} }
@ -958,7 +956,7 @@ func Test_writeQueryUse_examples(t *testing.T) {
), ),
eventstore.FilterPagination( eventstore.FilterPagination(
// used because we need to check for first login and an app which is not console // used because we need to check for first login and an app which is not console
eventstore.PositionGreater(decimal.NewFromInt(12), 4), eventstore.PositionGreater(12, 4),
), ),
), ),
eventstore.NewFilter( eventstore.NewFilter(
@ -1067,9 +1065,9 @@ func Test_writeQueryUse_examples(t *testing.T) {
"instance", "instance",
"user", "user",
"user.token.added", "user.token.added",
decimal.NewFromInt(12), float64(12),
uint32(4), uint32(4),
decimal.NewFromInt(12), float64(12),
"instance", "instance",
"instance", "instance",
[]string{"instance.idp.config.added", "instance.idp.oauth.added", "instance.idp.oidc.added", "instance.idp.jwt.added", "instance.idp.azure.added", "instance.idp.github.added", "instance.idp.github.enterprise.added", "instance.idp.gitlab.added", "instance.idp.gitlab.selfhosted.added", "instance.idp.google.added", "instance.idp.ldap.added", "instance.idp.config.apple.added", "instance.idp.saml.added"}, []string{"instance.idp.config.added", "instance.idp.oauth.added", "instance.idp.oidc.added", "instance.idp.jwt.added", "instance.idp.azure.added", "instance.idp.github.added", "instance.idp.github.enterprise.added", "instance.idp.gitlab.added", "instance.idp.gitlab.selfhosted.added", "instance.idp.google.added", "instance.idp.ldap.added", "instance.idp.config.apple.added", "instance.idp.saml.added"},
@ -1203,7 +1201,7 @@ func Test_executeQuery(t *testing.T) {
time.Now(), time.Now(),
"event.type", "event.type",
uint32(23), uint32(23),
decimal.NewFromInt(123).String(), float64(123),
uint32(0), uint32(0),
nil, nil,
"gigi", "gigi",
@ -1237,7 +1235,7 @@ func Test_executeQuery(t *testing.T) {
time.Now(), time.Now(),
"event.type", "event.type",
uint32(23), uint32(23),
decimal.NewFromInt(123).String(), float64(123),
uint32(0), uint32(0),
[]byte(`{"name": "gigi"}`), []byte(`{"name": "gigi"}`),
"gigi", "gigi",
@ -1271,7 +1269,7 @@ func Test_executeQuery(t *testing.T) {
time.Now(), time.Now(),
"event.type", "event.type",
uint32(23), uint32(23),
decimal.NewFromInt(123).String(), float64(123),
uint32(0), uint32(0),
nil, nil,
"gigi", "gigi",
@ -1285,7 +1283,7 @@ func Test_executeQuery(t *testing.T) {
time.Now(), time.Now(),
"event.type", "event.type",
uint32(24), uint32(24),
decimal.NewFromInt(124).String(), float64(124),
uint32(0), uint32(0),
[]byte(`{"name": "gigi"}`), []byte(`{"name": "gigi"}`),
"gigi", "gigi",
@ -1319,7 +1317,7 @@ func Test_executeQuery(t *testing.T) {
time.Now(), time.Now(),
"event.type", "event.type",
uint32(23), uint32(23),
decimal.NewFromInt(123).String(), float64(123),
uint32(0), uint32(0),
nil, nil,
"gigi", "gigi",
@ -1333,7 +1331,7 @@ func Test_executeQuery(t *testing.T) {
time.Now(), time.Now(),
"event.type", "event.type",
uint32(24), uint32(24),
decimal.NewFromInt(124).String(), float64(124),
uint32(0), uint32(0),
[]byte(`{"name": "gigi"}`), []byte(`{"name": "gigi"}`),
"gigi", "gigi",

View File

@ -7,8 +7,6 @@ import (
"slices" "slices"
"time" "time"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/v2/database" "github.com/zitadel/zitadel/internal/v2/database"
) )
@ -725,7 +723,7 @@ func (pc *PositionCondition) Min() *GlobalPosition {
// PositionGreater prepares the condition as follows // PositionGreater prepares the condition as follows
// if inPositionOrder is set: position = AND in_tx_order > OR or position > // if inPositionOrder is set: position = AND in_tx_order > OR or position >
// if inPositionOrder is NOT set: position > // if inPositionOrder is NOT set: position >
func PositionGreater(position decimal.Decimal, inPositionOrder uint32) paginationOpt { func PositionGreater(position float64, inPositionOrder uint32) paginationOpt {
return func(p *Pagination) { return func(p *Pagination) {
p.ensurePosition() p.ensurePosition()
p.position.min = &GlobalPosition{ p.position.min = &GlobalPosition{
@ -745,7 +743,7 @@ func GlobalPositionGreater(position *GlobalPosition) paginationOpt {
// PositionLess prepares the condition as follows // PositionLess prepares the condition as follows
// if inPositionOrder is set: position = AND in_tx_order > OR or position > // if inPositionOrder is set: position = AND in_tx_order > OR or position >
// if inPositionOrder is NOT set: position > // if inPositionOrder is NOT set: position >
func PositionLess(position decimal.Decimal, inPositionOrder uint32) paginationOpt { func PositionLess(position float64, inPositionOrder uint32) paginationOpt {
return func(p *Pagination) { return func(p *Pagination) {
p.ensurePosition() p.ensurePosition()
p.position.max = &GlobalPosition{ p.position.max = &GlobalPosition{

View File

@ -6,8 +6,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/v2/database" "github.com/zitadel/zitadel/internal/v2/database"
) )
@ -76,13 +74,13 @@ func TestPaginationOpt(t *testing.T) {
name: "global position greater", name: "global position greater",
args: args{ args: args{
opts: []paginationOpt{ opts: []paginationOpt{
GlobalPositionGreater(&GlobalPosition{Position: decimal.NewFromInt(10)}), GlobalPositionGreater(&GlobalPosition{Position: 10}),
}, },
}, },
want: &Pagination{ want: &Pagination{
position: &PositionCondition{ position: &PositionCondition{
min: &GlobalPosition{ min: &GlobalPosition{
Position: decimal.NewFromInt(10), Position: 10,
InPositionOrder: 0, InPositionOrder: 0,
}, },
}, },
@ -92,13 +90,13 @@ func TestPaginationOpt(t *testing.T) {
name: "position greater", name: "position greater",
args: args{ args: args{
opts: []paginationOpt{ opts: []paginationOpt{
PositionGreater(decimal.NewFromInt(10), 0), PositionGreater(10, 0),
}, },
}, },
want: &Pagination{ want: &Pagination{
position: &PositionCondition{ position: &PositionCondition{
min: &GlobalPosition{ min: &GlobalPosition{
Position: decimal.NewFromInt(10), Position: 10,
InPositionOrder: 0, InPositionOrder: 0,
}, },
}, },
@ -109,13 +107,13 @@ func TestPaginationOpt(t *testing.T) {
name: "position less", name: "position less",
args: args{ args: args{
opts: []paginationOpt{ opts: []paginationOpt{
PositionLess(decimal.NewFromInt(10), 12), PositionLess(10, 12),
}, },
}, },
want: &Pagination{ want: &Pagination{
position: &PositionCondition{ position: &PositionCondition{
max: &GlobalPosition{ max: &GlobalPosition{
Position: decimal.NewFromInt(10), Position: 10,
InPositionOrder: 12, InPositionOrder: 12,
}, },
}, },
@ -125,13 +123,13 @@ func TestPaginationOpt(t *testing.T) {
name: "global position less", name: "global position less",
args: args{ args: args{
opts: []paginationOpt{ opts: []paginationOpt{
GlobalPositionLess(&GlobalPosition{Position: decimal.NewFromInt(12), InPositionOrder: 24}), GlobalPositionLess(&GlobalPosition{Position: 12, InPositionOrder: 24}),
}, },
}, },
want: &Pagination{ want: &Pagination{
position: &PositionCondition{ position: &PositionCondition{
max: &GlobalPosition{ max: &GlobalPosition{
Position: decimal.NewFromInt(12), Position: 12,
InPositionOrder: 24, InPositionOrder: 24,
}, },
}, },
@ -142,19 +140,19 @@ func TestPaginationOpt(t *testing.T) {
args: args{ args: args{
opts: []paginationOpt{ opts: []paginationOpt{
PositionBetween( PositionBetween(
&GlobalPosition{decimal.NewFromInt(10), 12}, &GlobalPosition{10, 12},
&GlobalPosition{decimal.NewFromInt(20), 0}, &GlobalPosition{20, 0},
), ),
}, },
}, },
want: &Pagination{ want: &Pagination{
position: &PositionCondition{ position: &PositionCondition{
min: &GlobalPosition{ min: &GlobalPosition{
Position: decimal.NewFromInt(10), Position: 10,
InPositionOrder: 12, InPositionOrder: 12,
}, },
max: &GlobalPosition{ max: &GlobalPosition{
Position: decimal.NewFromInt(20), Position: 20,
InPositionOrder: 0, InPositionOrder: 0,
}, },
}, },

View File

@ -1,8 +1,6 @@
package readmodel package readmodel
import ( import (
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/v2/eventstore" "github.com/zitadel/zitadel/internal/v2/eventstore"
"github.com/zitadel/zitadel/internal/v2/system" "github.com/zitadel/zitadel/internal/v2/system"
"github.com/zitadel/zitadel/internal/v2/system/mirror" "github.com/zitadel/zitadel/internal/v2/system/mirror"
@ -10,7 +8,7 @@ import (
type LastSuccessfulMirror struct { type LastSuccessfulMirror struct {
ID string ID string
Position decimal.Decimal Position float64
source string source string
} }
@ -55,7 +53,7 @@ func (h *LastSuccessfulMirror) Reduce(events ...*eventstore.StorageEvent) (err e
func (h *LastSuccessfulMirror) reduceSucceeded(event *eventstore.StorageEvent) error { func (h *LastSuccessfulMirror) reduceSucceeded(event *eventstore.StorageEvent) error {
// if position is set we skip all older events // if position is set we skip all older events
if h.Position.GreaterThan(decimal.NewFromInt(0)) { if h.Position > 0 {
return nil return nil
} }

View File

@ -1,8 +1,6 @@
package mirror package mirror
import ( import (
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/v2/eventstore" "github.com/zitadel/zitadel/internal/v2/eventstore"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
@ -11,7 +9,7 @@ type succeededPayload struct {
// Source is the name of the database data are mirrored from // Source is the name of the database data are mirrored from
Source string `json:"source"` Source string `json:"source"`
// Position until data will be mirrored // Position until data will be mirrored
Position decimal.Decimal `json:"position"` Position float64 `json:"position"`
} }
const SucceededType = eventTypePrefix + "succeeded" const SucceededType = eventTypePrefix + "succeeded"
@ -40,7 +38,7 @@ func SucceededEventFromStorage(event *eventstore.StorageEvent) (e *SucceededEven
}, nil }, nil
} }
func NewSucceededCommand(source string, position decimal.Decimal) *eventstore.Command { func NewSucceededCommand(source string, position float64) *eventstore.Command {
return &eventstore.Command{ return &eventstore.Command{
Action: eventstore.Action[any]{ Action: eventstore.Action[any]{
Creator: Creator, Creator: Creator,

View File

@ -4,43 +4,42 @@ ZITADEL_HOST ?=
ADMIN_LOGIN_NAME ?= ADMIN_LOGIN_NAME ?=
ADMIN_PASSWORD ?= ADMIN_PASSWORD ?=
K6 := ./../../xk6-modules/k6
.PHONY: human_password_login .PHONY: human_password_login
human_password_login: bundle human_password_login: bundle
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/human_password_login.js --vus ${VUS} --duration ${DURATION} k6 run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/human_password_login.js --vus ${VUS} --duration ${DURATION}
.PHONY: machine_pat_login .PHONY: machine_pat_login
machine_pat_login: bundle machine_pat_login: bundle
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/machine_pat_login.js --vus ${VUS} --duration ${DURATION} k6 run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/machine_pat_login.js --vus ${VUS} --duration ${DURATION}
.PHONY: machine_client_credentials_login .PHONY: machine_client_credentials_login
machine_client_credentials_login: bundle machine_client_credentials_login: bundle
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/machine_client_credentials_login.js --vus ${VUS} --duration ${DURATION} k6 run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/machine_client_credentials_login.js --vus ${VUS} --duration ${DURATION}
.PHONY: user_info .PHONY: user_info
user_info: bundle user_info: bundle
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/user_info.js --vus ${VUS} --duration ${DURATION} k6 run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/user_info.js --vus ${VUS} --duration ${DURATION}
.PHONY: manipulate_user .PHONY: manipulate_user
manipulate_user: bundle manipulate_user: bundle
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/manipulate_user.js --vus ${VUS} --duration ${DURATION} k6 run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/manipulate_user.js --vus ${VUS} --duration ${DURATION}
.PHONY: introspect .PHONY: introspect
introspect: ensure_modules bundle introspect: ensure_modules bundle
go install go.k6.io/xk6/cmd/xk6@latest go install go.k6.io/xk6/cmd/xk6@latest
cd ../../xk6-modules && xk6 build --with xk6-zitadel=. cd ../../xk6-modules && xk6 build --with xk6-zitadel=.
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/introspection.js --vus ${VUS} --duration ${DURATION} ./../../xk6-modules/k6 run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/introspection.js --vus ${VUS} --duration ${DURATION}
.PHONY: add_session .PHONY: add_session
add_session: bundle add_session: bundle
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/session.js --vus ${VUS} --duration ${DURATION} k6 run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/session.js --vus ${VUS} --duration ${DURATION}
.PHONY: machine_jwt_profile_grant .PHONY: machine_jwt_profile_grant
machine_jwt_profile_grant: ensure_modules ensure_key_pair bundle machine_jwt_profile_grant: ensure_modules ensure_key_pair bundle
go install go.k6.io/xk6/cmd/xk6@latest go install go.k6.io/xk6/cmd/xk6@latest
cd ../../xk6-modules && xk6 build --with xk6-zitadel=. cd ../../xk6-modules && xk6 build --with xk6-zitadel=.
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/machine_jwt_profile_grant.js --vus ${VUS} --duration ${DURATION} ./../../xk6-modules/k6 run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/machine_jwt_profile_grant.js --iterations 1
# --vus ${VUS} --duration ${DURATION}
.PHONY: machine_jwt_profile_grant_single_user .PHONY: machine_jwt_profile_grant_single_user
machine_jwt_profile_grant_single_user: ensure_modules ensure_key_pair bundle machine_jwt_profile_grant_single_user: ensure_modules ensure_key_pair bundle
@ -65,8 +64,6 @@ endif
bundle: bundle:
npm i npm i
npm run bundle npm run bundle
go install go.k6.io/xk6/cmd/xk6@latest
cd ../../xk6-modules && xk6 build --with xk6-zitadel=.
.PHONY: ensure_key_pair .PHONY: ensure_key_pair
ensure_key_pair: ensure_key_pair:

View File

@ -45,4 +45,3 @@ export function teardown(data: any) {
removeOrg(data.org, data.tokens.accessToken); removeOrg(data.org, data.tokens.accessToken);
console.info('teardown: org removed'); console.info('teardown: org removed');
} }

View File

@ -437,7 +437,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP"; tags: "SMTP Configs";
deprecated: true; deprecated: true;
}; };
} }
@ -457,7 +457,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP"; tags: "SMTP Configs";
deprecated: true; deprecated: true;
}; };
} }
@ -478,7 +478,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP"; tags: "SMTP Configs";
deprecated: true; deprecated: true;
}; };
} }
@ -499,7 +499,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP"; tags: "SMTP Configs";
deprecated: true; deprecated: true;
}; };
} }
@ -520,7 +520,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP"; tags: "SMTP Configs";
deprecated: true; deprecated: true;
}; };
} }
@ -541,7 +541,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP Provider"; tags: "SMTP Configs";
summary: "Activate SMTP Provider"; summary: "Activate SMTP Provider";
description: "Activate an SMTP provider." description: "Activate an SMTP provider."
deprecated: true; deprecated: true;
@ -564,7 +564,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP Provider"; tags: "SMTP Configs";
deprecated: true; deprecated: true;
}; };
} }
@ -584,7 +584,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP"; tags: "SMTP Configs";
deprecated: true; deprecated: true;
}; };
} }
@ -605,7 +605,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP Provider"; tags: "SMTP Configs";
deprecated: true; deprecated: true;
}; };
} }
@ -626,7 +626,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP Provider"; tags: "SMTP Configs";
deprecated: true; deprecated: true;
}; };
} }
@ -663,7 +663,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Email providers"; tags: "Email Provider";
summary: "List Email providers"; summary: "List Email providers";
description: "Returns a list of Email providers." description: "Returns a list of Email providers."
}; };
@ -679,7 +679,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Email"; tags: "Email Provider";
summary: "Get active Email provider"; summary: "Get active Email provider";
description: "Returns the active Email provider from the system. This is used to send E-Mails to the users." description: "Returns the active Email provider from the system. This is used to send E-Mails to the users."
}; };
@ -695,7 +695,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Email"; tags: "Email Provider";
summary: "Get Email provider by its id"; summary: "Get Email provider by its id";
description: "Get a specific Email provider by its ID."; description: "Get a specific Email provider by its ID.";
}; };
@ -712,7 +712,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Email"; tags: "Email Provider";
summary: "Add SMTP Email provider"; summary: "Add SMTP Email provider";
description: "Add a new SMTP Email provider if nothing is set yet." description: "Add a new SMTP Email provider if nothing is set yet."
}; };
@ -729,7 +729,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Email"; tags: "Email Provider";
summary: "Update SMTP Email provider"; summary: "Update SMTP Email provider";
description: "Update the SMTP Email provider, be aware that this will be activated as soon as it is saved. So the users will get notifications from the newly configured SMTP." description: "Update the SMTP Email provider, be aware that this will be activated as soon as it is saved. So the users will get notifications from the newly configured SMTP."
}; };
@ -746,7 +746,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Email"; tags: "Email Provider";
summary: "Add HTTP Email provider"; summary: "Add HTTP Email provider";
description: "Add a new HTTP Email provider if nothing is set yet." description: "Add a new HTTP Email provider if nothing is set yet."
}; };
@ -763,7 +763,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Email"; tags: "Email Provider";
summary: "Update HTTP Email provider"; summary: "Update HTTP Email provider";
description: "Update the HTTP Email provider, be aware that this will be activated as soon as it is saved. So the users will get notifications from the newly configured HTTP." description: "Update the HTTP Email provider, be aware that this will be activated as soon as it is saved. So the users will get notifications from the newly configured HTTP."
}; };
@ -780,7 +780,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP"; tags: "Email Provider";
summary: "Update SMTP Password"; summary: "Update SMTP Password";
description: "Update the SMTP password that is used for the host, be aware that this will be activated as soon as it is saved. So the users will get notifications from the newly configured SMTP." description: "Update the SMTP password that is used for the host, be aware that this will be activated as soon as it is saved. So the users will get notifications from the newly configured SMTP."
}; };
@ -830,7 +830,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Email"; tags: "Email Provider";
summary: "Remove Email provider"; summary: "Remove Email provider";
description: "Remove the Email provider, be aware that the users will not get an E-Mail if no provider is set." description: "Remove the Email provider, be aware that the users will not get an E-Mail if no provider is set."
}; };
@ -847,7 +847,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP Email Provider"; tags: "Email Provider";
summary: "Test SMTP Email Provider"; summary: "Test SMTP Email Provider";
description: "Test an SMTP Email provider identified by its ID. After testing the provider, the users will receive information about the test results." description: "Test an SMTP Email provider identified by its ID. After testing the provider, the users will receive information about the test results."
}; };
@ -864,7 +864,7 @@ service AdminService {
}; };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP EMailProvider"; tags: "Email Provider";
summary: "Test SMTP Email Provider"; summary: "Test SMTP Email Provider";
description: "Test an SMTP Email provider. After testing the provider, the users will receive information about the test results." description: "Test an SMTP Email provider. After testing the provider, the users will receive information about the test results."
}; };

View File

@ -1565,6 +1565,11 @@ message UserGrant {
description: "type of the user (human / machine)" description: "type of the user (human / machine)"
} }
]; ];
zitadel.user.v1.UserGrantState state = 13 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "current state of the user grant";
}
];
} }
message ListMyProjectOrgsRequest { message ListMyProjectOrgsRequest {