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)
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.
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)
- [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
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

View File

@ -3,8 +3,6 @@ package mirror
import (
"context"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/v2/eventstore"
"github.com/zitadel/zitadel/internal/v2/projection"
"github.com/zitadel/zitadel/internal/v2/readmodel"
@ -32,12 +30,12 @@ func queryLastSuccessfulMigration(ctx context.Context, destinationES *eventstore
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
if len(instanceIDs) > 0 {
cmd, err = mirror_event.NewStartedInstancesCommand(destination, instanceIDs)
if err != nil {
return decimal.Decimal{}, err
return 0, err
}
} else {
cmd = mirror_event.NewStartedSystemCommand(destination)
@ -60,12 +58,12 @@ func writeMigrationStart(ctx context.Context, sourceES *eventstore.EventStore, i
),
)
if err != nil {
return decimal.Decimal{}, err
return 0, err
}
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(
ctx,
eventstore.NewPushIntent(

View File

@ -9,7 +9,6 @@ import (
"time"
"github.com/jackc/pgx/v5/stdlib"
"github.com/shopspring/decimal"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"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")
}
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))
for err := range errs {
joinedErrs = append(joinedErrs, err)

View File

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

View File

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

View File

@ -154,10 +154,25 @@
</td>
</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>
<th mat-header-cell *matHeaderCellDef class="user-tr-actions"></th>
<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
actions
matTooltip="{{ 'ACTIONS.REMOVE' | translate }}"

View File

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

View File

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

View File

@ -42,7 +42,16 @@
[context]="UserGrantContext.GRANTED_PROJECT"
[projectId]="projectId"
[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"
[disableDelete]="(['user.grant.delete$', 'user.grant.delete:' + grantId] | hasRole | async) === false"
[refreshOnPreviousRoutes]="['/grant-create/project/{{projectId}}/grant/{{grantId}}']"

View File

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

View File

@ -136,7 +136,16 @@
<cnsl-user-grants
[userId]="user.id"
[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"
[disableDelete]="(['user.grant.delete$'] | hasRole | async) === false"
>

View File

@ -222,7 +222,7 @@
<cnsl-user-grants
[userId]="user.id"
[context]="USERGRANTCONTEXT"
[displayedColumns]="['select', 'projectId', 'creationDate', 'changeDate', 'roleNamesList', 'actions']"
[displayedColumns]="['select', 'projectId', 'creationDate', 'changeDate', 'state', 'roleNamesList', 'actions']"
[disableWrite]="(['user.grant.write$'] | 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/client`: Core components for establishing a client 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
- `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 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.
Right after the user is found, a session is created and set as cookie.
This cookie is then hydrated with more information once the users continues.
The /loginname page allows to enter loginname or email of a user which is then used to search for a user.
If the user is found, a session is created and set as cookie, then the user is redirected to the available authentication method page.
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.
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.
@ -91,9 +92,9 @@ The application can then request a token calling the /token endpoint of the logi
- [x] Passkey
- [x] IDPs
- [x] Google
- [ ] GitHub
- [x] GitHub
- [x] GitLab
- [ ] Azure
- [x] Azure
- [ ] Apple
- Register
- [x] Email Password
@ -113,7 +114,7 @@ Authenticated users are directly able to self service their account.
- [x] OTP via email
- [x] OTP via SMS
- [x] Setup Multifactor Passkey (U2F)
- [ ] Validate Account (email verification)
- [x] Validate Account (email verification)
### 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
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).
After your domain has been verified, you can reconfigure your DNS settings in order to deploy the login on your own.
#### OIDC Proxy
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
@ -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`.
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.
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)

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 UpstreamHeader from '/docs/self-hosting/deploy/troubleshooting/_upstream_header.mdx'
## 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/behavior",
"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/hashicorp/golang-lru/v2 v2.0.7
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/jarcoal/jpath v0.0.0-20140328210829-f76b8b2dbf52
github.com/jinzhu/gorm v1.9.16
@ -55,7 +54,6 @@ require (
github.com/rakyll/statik v0.1.7
github.com/rs/cors v1.11.0
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/spf13/cobra v1.8.1
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/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/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/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
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/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
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/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
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 {
keys map[string]*SystemAPIUser
mutex sync.Mutex
mutex sync.RWMutex
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) {
s.mutex.RLock()
cachedKey, ok := s.cachedKeys[userID]
s.mutex.RUnlock()
if ok {
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 {
return &settings_pb.SMTPConfig{
Details: object.ToViewDetailsPb(config.Sequence, config.CreationDate, config.ChangeDate, config.ResourceOwner),
Id: config.ID,
Description: config.Description,
Tls: config.SMTPConfig.TLS,
Host: config.SMTPConfig.Host,
User: config.SMTPConfig.User,
State: SMTPConfigStateToPb(config.State),
SenderAddress: config.SMTPConfig.SenderAddress,
SenderName: config.SMTPConfig.SenderName,
ret := &settings_pb.SMTPConfig{
Details: object.ToViewDetailsPb(config.Sequence, config.CreationDate, config.ChangeDate, config.ResourceOwner),
Id: config.ID,
Description: config.Description,
State: SMTPConfigStateToPb(config.State),
}
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 {

View File

@ -55,5 +55,6 @@ func UserGrantToPb(grant *query.UserGrant) *auth_pb.UserGrant {
ProjectGrantId: grant.GrantID,
RoleKeys: grant.Roles,
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{
Id: grant.ID,
UserId: grant.UserID,
State: user_pb.UserGrantState_USER_GRANT_STATE_ACTIVE,
State: UserGrantStateToPb(grant.State),
RoleKeys: grant.Roles,
ProjectId: grant.ProjectID,
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) {
q = make([]query.SearchQuery, len(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)
span.EndWithError(err)
}()
client, err := o.query.GetOIDCClientByID(ctx, id, false)
client, err := o.query.ActiveOIDCClientByID(ctx, id, false)
if err != nil {
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
}
@ -333,6 +330,9 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo *oidc.UserInfo, us
if err != nil {
return err
}
if user.State != domain.UserStateActive {
return zerrors.ThrowUnauthenticated(nil, "OIDC-S3tha", "Errors.Users.NotActive")
}
var allRoles bool
roles := make([]string, 0)
for _, scope := range scopes {
@ -802,19 +802,24 @@ func (o *OPStorage) assertRoles(ctx context.Context, userID, applicationID strin
if projectID != "" {
roleAudience = append(roleAudience, projectID)
}
queries := make([]query.SearchQuery, 0, 2)
projectQuery, err := query.NewUserGrantProjectIDsSearchQuery(roleAudience)
if err != nil {
return nil, nil, err
}
queries = append(queries, projectQuery)
userIDQuery, err := query.NewUserGrantUserIDSearchQuery(userID)
if err != nil {
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{
Queries: queries,
Queries: []query.SearchQuery{
projectQuery,
userIDQuery,
activeQuery,
},
}, true)
if err != nil {
return nil, nil, err
@ -979,16 +984,13 @@ func (s *Server) VerifyClient(ctx context.Context, r *op.Request[op.ClientCreden
if err != nil {
return nil, err
}
client, err := s.query.GetOIDCClientByID(ctx, clientID, assertion)
client, err := s.query.ActiveOIDCClientByID(ctx, clientID, assertion)
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 {
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 {
client.Settings = &query.OIDCSettings{
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())
project, err := Instance.CreateProject(CTX)
require.NoError(t, err)
projectInactive, err := Instance.CreateProject(CTX)
require.NoError(t, err)
inactiveClient, err := Instance.CreateOIDCInactivateClient(CTX, redirectURI, logoutRedirectURI, project.GetId())
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)
require.NoError(t, err)
basicWebClient, err := Instance.CreateOIDCWebClientBasic(CTX, redirectURI, logoutRedirectURI, project.GetId())
@ -240,6 +244,14 @@ func TestServer_VerifyClient(t *testing.T) {
},
wantErr: true,
},
{
name: "client inactive (project) error",
client: clientDetails{
authReqClientID: nativeClient.GetClientId(),
clientID: inactiveProjectClient.GetClientId(),
},
wantErr: true,
},
{
name: "native client success",
client: clientDetails{

View File

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

View File

@ -213,7 +213,7 @@ func (s *Server) clientFromCredentials(ctx context.Context, cc *op.ClientCredent
if err != nil {
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) {
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/jonboulle/clockwork"
"github.com/muhlemmer/gu"
"github.com/shopspring/decimal"
"github.com/zitadel/logging"
"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 {
return o.privateKeyToSigningKey(selectSigningKey(keys.Keys))
}
var position decimal.Decimal
var position float64
if keys.State != nil {
position = keys.State.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)
if err != nil || !ok {
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", "")
}
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)
if err != nil {
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) {
@ -413,9 +412,9 @@ func (o *OPStorage) lockAndGenerateSigningKeyPair(ctx context.Context, algorithm
return o.command.GenerateSigningKeyPair(setOIDCCtx(ctx), algorithm)
}
func (o *OPStorage) getMaxKeySequence(ctx context.Context) (decimal.Decimal, error) {
return o.eventstore.LatestPosition(ctx,
eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxPosition).
func (o *OPStorage) getMaxKeySequence(ctx context.Context) (float64, error) {
return o.eventstore.LatestSequence(ctx,
eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxSequence).
ResourceOwner(authz.GetInstance(ctx).InstanceID()).
AwaitOpenTransactions().
AllowTimeTravel().

View File

@ -66,7 +66,10 @@ func (s *Server) UserInfo(ctx context.Context, r *op.Request[oidc.UserInfoReques
false,
)(ctx, true, domain.TriggerTypePreUserinfoCreation)
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
}
@ -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 {
out := new(oidc.UserInfo)
out := &oidc.UserInfo{
Subject: user.User.ID,
}
for _, s := range scope {
switch s {
case oidc.ScopeOpenID:
out.Subject = user.User.ID
case oidc.ScopeEmail:
if !userInfoAssertion {
continue

View File

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

View File

@ -6,7 +6,6 @@ import (
"time"
"github.com/go-jose/go-jose/v4"
"github.com/shopspring/decimal"
"github.com/zitadel/logging"
"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))
}
var position decimal.Decimal
var position float64
if certs.State != nil {
position = certs.State.Position
}
@ -88,7 +87,7 @@ func (p *Storage) getCertificateAndKey(ctx context.Context, usage crypto.KeyUsag
func (p *Storage) refreshCertificate(
ctx context.Context,
usage crypto.KeyUsage,
position decimal.Decimal,
position float64,
) error {
ok, err := p.ensureIsLatestCertificate(ctx, position)
if err != nil {
@ -104,12 +103,12 @@ func (p *Storage) refreshCertificate(
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)
if err != nil {
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 {
@ -152,9 +151,9 @@ func (p *Storage) lockAndGenerateCertificateAndKey(ctx context.Context, usage cr
}
}
func (p *Storage) getMaxKeySequence(ctx context.Context) (decimal.Decimal, error) {
return p.eventstore.LatestPosition(ctx,
eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxPosition).
func (p *Storage) getMaxKeySequence(ctx context.Context) (float64, error) {
return p.eventstore.LatestSequence(ctx,
eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxSequence).
ResourceOwner(authz.GetInstance(ctx).InstanceID()).
AwaitOpenTransactions().
AddQuery().

View File

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

View File

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

View File

@ -1058,6 +1058,74 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
[]domain.NextStep{&domain.VerifyInviteStep{}},
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",
fields{

View File

@ -11,6 +11,7 @@ import (
sd "github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
eventstore2 "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/query"
@ -119,7 +120,11 @@ func (q queryViewWrapper) UserGrantsByProjectAndUserID(ctx context.Context, proj
if err != nil {
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)
if err != nil {
return nil, err

View File

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

View File

@ -126,7 +126,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
pushErr := errors.New("pushErr")
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@ -149,7 +149,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
{
name: "not found error",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(),
),
},
@ -169,7 +169,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
{
name: "push error",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(eventFromEventPusherWithInstanceID(
"instance1",
deviceauth.NewAddedEvent(
@ -211,7 +211,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
{
name: "success",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(eventFromEventPusherWithInstanceID(
"instance1",
deviceauth.NewAddedEvent(
@ -256,7 +256,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
}
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)
@ -271,7 +271,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
pushErr := errors.New("pushErr")
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@ -288,7 +288,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
{
name: "not found error",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(),
),
},
@ -298,7 +298,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
{
name: "push error",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(eventFromEventPusherWithInstanceID(
"instance1",
deviceauth.NewAddedEvent(
@ -323,7 +323,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
{
name: "success/denied",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(eventFromEventPusherWithInstanceID(
"instance1",
deviceauth.NewAddedEvent(
@ -350,7 +350,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
{
name: "success/expired",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(eventFromEventPusherWithInstanceID(
"instance1",
deviceauth.NewAddedEvent(
@ -378,7 +378,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
}
gotDetails, err := c.CancelDeviceAuth(tt.args.ctx, tt.args.id, tt.args.reason)
require.ErrorIs(t, err, tt.wantErr)
@ -586,6 +586,69 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
},
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",
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
expectPush(
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
expectPush(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,

View File

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

View File

@ -80,7 +80,7 @@ func (c *Commands) CreateOIDCSessionFromAuthRequest(ctx context.Context, authReq
return nil, "", err
}
cmd, err := c.newOIDCSessionAddEvents(ctx, sessionModel.UserResourceOwner)
cmd, err := c.newOIDCSessionAddEvents(ctx, sessionModel.UserID, sessionModel.UserResourceOwner)
if err != nil {
return nil, "", err
}
@ -141,7 +141,7 @@ func (c *Commands) CreateOIDCSession(ctx context.Context,
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
cmd, err := c.newOIDCSessionAddEvents(ctx, resourceOwner)
cmd, err := c.newOIDCSessionAddEvents(ctx, userID, resourceOwner)
if err != nil {
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))
}
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)
if err != nil {
return nil, err
@ -281,6 +288,7 @@ func (c *Commands) newOIDCSessionAddEvents(ctx context.Context, resourceOwner st
encryptionAlg: c.keyAlgorithm,
events: pending,
oidcSessionWriteModel: NewOIDCSessionWriteModel(sessionID, resourceOwner),
userStateModel: userStateModel,
accessTokenLifetime: accessTokenLifetime,
refreshTokenLifeTime: refreshTokenLifeTime,
refreshTokenIdleLifetime: refreshTokenIdleLifetime,
@ -321,6 +329,13 @@ func (c *Commands) newOIDCSessionUpdateEvents(ctx context.Context, refreshToken
if err = sessionWriteModel.CheckRefreshToken(refreshTokenID); err != nil {
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)
if err != nil {
return nil, err
@ -342,6 +357,7 @@ type OIDCSessionEvents struct {
encryptionAlg crypto.EncryptionAlgorithm
events []eventstore.Command
oidcSessionWriteModel *OIDCSessionWriteModel
userStateModel *UserV2WriteModel
accessTokenLifetime 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"),
},
},
{
"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",
fields{
@ -266,6 +363,21 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
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
expectPush(
authrequest.NewCodeExchangedEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate),
@ -382,6 +494,21 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
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
expectPush(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -521,10 +648,81 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
},
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",
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,
),
),
expectFilter(), // token lifetime
expectPush(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -606,6 +804,21 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
name: "with refresh token",
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,
),
),
expectFilter(), // token lifetime
expectPush(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -689,6 +902,21 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
name: "with sessionID",
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,
),
),
expectFilter(), // token lifetime
expectPush(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -772,6 +1000,21 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
name: "impersonation not allowed",
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,
),
),
expectFilter(), // token lifetime
),
idGenerator: mock.NewIDGeneratorExpectIDs(t, "oidcSessionID"),
@ -813,6 +1056,21 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
name: "impersonation allowed",
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,
),
),
expectFilter(), // token lifetime
expectPush(
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"),
},
},
{
"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",
fields{
@ -1088,6 +1403,21 @@ func TestCommands_ExchangeOIDCSessionRefreshAndAccessToken(t *testing.T) {
"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
expectPush(
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) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
idGenerator id.Generator
defaultAccessTokenLifetime time.Duration
defaultRefreshTokenLifetime time.Duration
@ -1177,7 +1507,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
{
"invalid refresh token format error",
fields{
eventstore: eventstoreExpect(t),
eventstore: expectEventstore(),
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args{
@ -1191,7 +1521,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
{
"inactive session error",
fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(),
),
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
@ -1207,7 +1537,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
{
"invalid refresh token error",
fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1235,7 +1565,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
{
"expired refresh token error",
fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1267,7 +1597,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
{
"get successful",
fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusherWithCreationDateNow(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1316,7 +1646,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
defaultAccessTokenLifetime: tt.fields.defaultAccessTokenLifetime,
defaultRefreshTokenLifetime: tt.fields.defaultRefreshTokenLifetime,
@ -1348,7 +1678,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
keyAlgorithm crypto.EncryptionAlgorithm
}
type args struct {
@ -1368,7 +1698,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
{
"invalid token",
fields{
eventstore: eventstoreExpect(t),
eventstore: expectEventstore(),
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args{
@ -1382,7 +1712,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
{
"refresh_token inactive",
fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1407,7 +1737,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
{
"refresh_token invalid client",
fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1432,7 +1762,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
{
"refresh_token revoked",
fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1468,7 +1798,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
{
"access_token inactive session",
fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1493,7 +1823,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
{
"access_token invalid client",
fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1518,7 +1848,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
{
"access_token revoked",
fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
@ -1555,7 +1885,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
keyAlgorithm: tt.fields.keyAlgorithm,
}
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:
if wm.ID != e.ID {
wm.State = domain.SMSConfigStateInactive
continue
}
wm.State = domain.SMSConfigStateActive
@ -110,6 +111,7 @@ func (wm *IAMSMSConfigWriteModel) Reduce() error {
wm.State = domain.SMSConfigStateRemoved
case *instance.SMSConfigActivatedEvent:
if wm.ID != e.ID {
wm.State = domain.SMSConfigStateInactive
continue
}
wm.State = domain.SMSConfigStateActive

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,6 @@ import (
"time"
"github.com/jackc/pgx/v5/pgconn"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/eventstore"
)
@ -124,7 +123,7 @@ func (h *FieldHandler) processEvents(ctx context.Context, config *triggerConfig)
return additionalIteration, err
}
// 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
}
@ -157,7 +156,7 @@ func (h *FieldHandler) fetchEvents(ctx context.Context, tx *sql.Tx, 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
}
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) {
var position decimal.Decimal
var position float64
for i, event := range events {
if !event.Position().Equal(position) {
if event.Position() != position {
offset = 0
position = event.Position()
}

View File

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

View File

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

View File

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

View File

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

View File

@ -2,16 +2,13 @@ package eventstore_test
import (
"context"
"database/sql"
"encoding/json"
"os"
"testing"
"time"
"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/zitadel/cmd/initialise"
@ -42,19 +39,10 @@ func TestMain(m *testing.M) {
testCRDBClient = &database.DB{
Database: new(testDB),
}
config, err := pgxpool.ParseConfig(ts.PGURL().String())
testCRDBClient.DB, err = sql.Open("postgres", ts.PGURL().String())
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 {
logging.WithFields("error", err).Fatal("unable to ping db")
}
@ -115,19 +103,10 @@ func initDB(db *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 {
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 {
return nil, err
}

View File

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

View File

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

View File

@ -13,7 +13,6 @@ import (
context "context"
reflect "reflect"
decimal "github.com/shopspring/decimal"
eventstore "github.com/zitadel/zitadel/internal/eventstore"
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)
}
// LatestPosition mocks base method.
func (m *MockQuerier) LatestPosition(arg0 context.Context, arg1 *eventstore.SearchQueryBuilder) (decimal.Decimal, error) {
// LatestSequence mocks base method.
func (m *MockQuerier) LatestSequence(arg0 context.Context, arg1 *eventstore.SearchQueryBuilder) (float64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LatestPosition", arg0, arg1)
ret0, _ := ret[0].(decimal.Decimal)
ret := m.ctrl.Call(m, "LatestSequence", arg0, arg1)
ret0, _ := ret[0].(float64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// LatestPosition indicates an expected call of LatestPosition.
func (mr *MockQuerierMockRecorder) LatestPosition(arg0, arg1 any) *gomock.Call {
// LatestSequence indicates an expected call of LatestSequence.
func (mr *MockQuerierMockRecorder) LatestSequence(arg0, arg1 any) *gomock.Call {
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.

View File

@ -7,7 +7,6 @@ import (
"testing"
"time"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
@ -187,8 +186,8 @@ func (e *mockEvent) Sequence() uint64 {
return e.sequence
}
func (e *mockEvent) Position() decimal.Decimal {
return decimal.Decimal{}
func (e *mockEvent) Position() float64 {
return 0
}
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
OperationGreaterEqual
operationCount
)
@ -234,10 +232,10 @@ func instanceIDsFilter(builder *eventstore.SearchQueryBuilder, query *SearchQuer
}
func positionAfterFilter(builder *eventstore.SearchQueryBuilder, query *SearchQuery) *Filter {
if builder.GetPositionAfter().IsZero() {
if builder.GetPositionAfter() == 0 {
return nil
}
query.Position = NewFilter(FieldPosition, builder.GetPositionAfter(), OperationGreaterEqual)
query.Position = NewFilter(FieldPosition, builder.GetPositionAfter(), OperationGreater)
return query.Position
}

View File

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

View File

@ -4,8 +4,6 @@ import (
"database/sql"
"testing"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/eventstore"
"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},
Typ: "test.created",
Version: "v1",
Pos: decimal.NewFromInt(42),
Pos: 42,
}
for _, opt := range opts {

View File

@ -9,7 +9,6 @@ import (
"strconv"
"strings"
"github.com/shopspring/decimal"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/call"
@ -26,7 +25,7 @@ type querier interface {
conditionFormat(repository.Operation) string
placeholder(query string) string
eventQuery(useV1 bool) string
maxPositionQuery(useV1 bool) string
maxSequenceQuery(useV1 bool) string
instanceIDsQuery(useV1 bool) string
db() *database.DB
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)
// we select the most recent row
if q.Columns == eventstore.ColumnsMaxPosition {
if q.Columns == eventstore.ColumnsMaxSequence {
q.Limit = 1
q.Desc = true
}
@ -92,7 +91,7 @@ func query(ctx context.Context, criteria querier, searchQuery *eventstore.Search
switch q.Columns {
case eventstore.ColumnsEvent,
eventstore.ColumnsMaxPosition:
eventstore.ColumnsMaxSequence:
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) {
switch columns {
case eventstore.ColumnsMaxPosition:
return criteria.maxPositionQuery(useV1), maxPositionScanner
case eventstore.ColumnsMaxSequence:
return criteria.maxSequenceQuery(useV1), maxSequenceScanner
case eventstore.ColumnsInstanceIDs:
return criteria.instanceIDsQuery(useV1), instanceIDsScanner
case eventstore.ColumnsEvent:
@ -155,15 +154,13 @@ func prepareTimeTravel(ctx context.Context, criteria querier, allow bool) string
return criteria.Timetravel(took)
}
func maxPositionScanner(row scan, dest interface{}) (err error) {
position, ok := dest.(*decimal.Decimal)
func maxSequenceScanner(row scan, dest interface{}) (err error) {
position, ok := dest.(*sql.NullFloat64)
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(&res)
err = row(position)
if err == nil || errors.Is(err, sql.ErrNoRows) {
*position = res.Decimal
return nil
}
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)
}
event := new(repository.Event)
position := new(decimal.NullDecimal)
position := new(sql.NullFloat64)
if useV1 {
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")
return zerrors.ThrowInternal(err, "SQL-M0dsf", "unable to scan row")
}
event.Pos = position.Decimal
event.Pos = position.Float64
return reduce(event)
}
}

View File

@ -10,7 +10,6 @@ import (
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/internal/database"
@ -110,36 +109,36 @@ func Test_prepareColumns(t *testing.T) {
{
name: "max column",
args: args{
columns: eventstore.ColumnsMaxPosition,
dest: new(decimal.Decimal),
columns: eventstore.ColumnsMaxSequence,
dest: new(sql.NullFloat64),
useV1: true,
},
res: res{
query: `SELECT event_sequence FROM eventstore.events`,
expected: decimal.NewFromInt(42),
expected: sql.NullFloat64{Float64: 43, Valid: true},
},
fields: fields{
dbRow: []interface{}{decimal.NewNullDecimal(decimal.NewFromInt(42))},
dbRow: []interface{}{sql.NullFloat64{Float64: 43, Valid: true}},
},
},
{
name: "max column v2",
args: args{
columns: eventstore.ColumnsMaxPosition,
dest: new(decimal.Decimal),
columns: eventstore.ColumnsMaxSequence,
dest: new(sql.NullFloat64),
},
res: res{
query: `SELECT "position" FROM eventstore.events2`,
expected: decimal.NewFromInt(42),
expected: sql.NullFloat64{Float64: 43, Valid: true},
},
fields: fields{
dbRow: []interface{}{decimal.NewNullDecimal(decimal.NewFromInt(42))},
dbRow: []interface{}{sql.NullFloat64{Float64: 43, Valid: true}},
},
},
{
name: "max sequence wrong dest type",
args: args{
columns: eventstore.ColumnsMaxPosition,
columns: eventstore.ColumnsMaxSequence,
dest: new(uint64),
},
res: res{
@ -179,11 +178,11 @@ func Test_prepareColumns(t *testing.T) {
res: res{
query: `SELECT created_at, event_type, "sequence", "position", payload, creator, "owner", instance_id, aggregate_type, aggregate_id, revision FROM eventstore.events2`,
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{
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{
query: `SELECT created_at, event_type, "sequence", "position", payload, creator, "owner", instance_id, aggregate_type, aggregate_id, revision FROM eventstore.events2`,
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{
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"
"time"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/zerrors"
)
@ -25,7 +23,7 @@ type SearchQueryBuilder struct {
queries []*SearchQuery
tx *sql.Tx
allowTimeTravel bool
positionGreaterEqual decimal.Decimal
positionAfter float64
awaitOpenTransactions bool
creationDateAfter time.Time
creationDateBefore time.Time
@ -76,8 +74,8 @@ func (b *SearchQueryBuilder) GetAllowTimeTravel() bool {
return b.allowTimeTravel
}
func (b SearchQueryBuilder) GetPositionAfter() decimal.Decimal {
return b.positionGreaterEqual
func (b SearchQueryBuilder) GetPositionAfter() float64 {
return b.positionAfter
}
func (b SearchQueryBuilder) GetAwaitOpenTransactions() bool {
@ -133,8 +131,8 @@ type Columns int8
const (
//ColumnsEvent represents all fields of an event
ColumnsEvent = iota + 1
// ColumnsMaxPosition represents the latest sequence of the filtered events
ColumnsMaxPosition
// ColumnsMaxSequence represents the latest sequence of the filtered events
ColumnsMaxSequence
// ColumnsInstanceIDs represents the instance ids of the filtered events
ColumnsInstanceIDs
@ -269,8 +267,8 @@ func (builder *SearchQueryBuilder) AllowTimeTravel() *SearchQueryBuilder {
}
// PositionAfter filters for events which happened after the specified time
func (builder *SearchQueryBuilder) PositionGreaterEqual(position decimal.Decimal) *SearchQueryBuilder {
builder.positionGreaterEqual = position
func (builder *SearchQueryBuilder) PositionAfter(position float64) *SearchQueryBuilder {
builder.positionAfter = position
return builder
}

View File

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

View File

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

View File

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

View File

@ -22,6 +22,7 @@ import (
"github.com/zitadel/zitadel/pkg/grpc/authn"
"github.com/zitadel/zitadel/pkg/grpc/management"
"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) {
@ -107,6 +108,20 @@ func (i *Instance) CreateOIDCInactivateClient(ctx context.Context, redirectURI,
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) {
project, err := i.Client.Mgmt.AddProject(ctx, &management.AddProjectRequest{
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
}
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) {
name = gofakeit.Username()
machine, err = i.Client.Mgmt.AddMachineUser(ctx, &management.AddMachineUserRequest{

View File

@ -5,7 +5,6 @@ import (
"strings"
"time"
"github.com/shopspring/decimal"
"golang.org/x/text/language"
"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)
// 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)
defer func() { span.EndWithError(err) }()
@ -163,7 +162,7 @@ func (q *Queries) checkSessionNotTerminatedAfter(ctx context.Context, sessionID,
}
type sessionTerminatedModel struct {
position decimal.Decimal
position float64
sessionID string
userID string
fingerPrintID string
@ -183,7 +182,7 @@ func (s *sessionTerminatedModel) AppendEvents(events ...eventstore.Event) {
func (s *sessionTerminatedModel) Query() *eventstore.SearchQueryBuilder {
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
PositionGreaterEqual(s.position).
PositionAfter(s.position).
AddQuery().
AggregateTypes(session.AggregateType).
AggregateIDs(s.sessionID).

View File

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

View File

@ -1,6 +1,7 @@
package query
import (
"context"
"database/sql"
"database/sql/driver"
"errors"
@ -9,13 +10,15 @@ import (
"testing"
"time"
sq "github.com/Masterminds/squirrel"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/zerrors"
)
var (
expectedAppQuery = regexp.QuoteMeta(`SELECT projections.apps7.id,` +
expectedAppQueryBase = `SELECT projections.apps7.id,` +
` projections.apps7.name,` +
` projections.apps7.project_id,` +
` projections.apps7.creation_date,` +
@ -53,8 +56,11 @@ var (
` 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_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` +
` AS OF SYSTEM TIME '-1 ms'`)
` 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`
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,` +
` projections.apps7.name,` +
` projections.apps7.project_id,` +
@ -1140,8 +1146,10 @@ func Test_AppPrepare(t *testing.T) {
object interface{}
}{
{
name: "prepareAppQuery no result",
prepare: prepareAppQuery,
name: "prepareAppQuery no result",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{
sqlExpectations: mockQueriesScanErr(
expectedAppQuery,
@ -1158,8 +1166,10 @@ func Test_AppPrepare(t *testing.T) {
object: (*App)(nil),
},
{
name: "prepareAppQuery found",
prepare: prepareAppQuery,
name: "prepareAppQuery found",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{
sqlExpectations: mockQuery(
expectedAppQuery,
@ -1215,8 +1225,10 @@ func Test_AppPrepare(t *testing.T) {
},
},
{
name: "prepareAppQuery api app",
prepare: prepareAppQuery,
name: "prepareAppQuery api app",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{
sqlExpectations: mockQueries(
expectedAppQuery,
@ -1278,8 +1290,10 @@ func Test_AppPrepare(t *testing.T) {
},
},
{
name: "prepareAppQuery oidc app",
prepare: prepareAppQuery,
name: "prepareAppQuery oidc app",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{
sqlExpectations: mockQueries(
expectedAppQuery,
@ -1355,9 +1369,93 @@ func Test_AppPrepare(t *testing.T) {
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{
sqlExpectations: mockQueries(
expectedAppQuery,
@ -1420,8 +1518,10 @@ func Test_AppPrepare(t *testing.T) {
},
},
{
name: "prepareAppQuery oidc app IsDevMode inactive",
prepare: prepareAppQuery,
name: "prepareAppQuery oidc app IsDevMode inactive",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{
sqlExpectations: mockQueries(
expectedAppQuery,
@ -1499,8 +1599,10 @@ func Test_AppPrepare(t *testing.T) {
},
},
{
name: "prepareAppQuery oidc app AssertAccessTokenRole inactive",
prepare: prepareAppQuery,
name: "prepareAppQuery oidc app AssertAccessTokenRole inactive",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{
sqlExpectations: mockQueries(
expectedAppQuery,
@ -1578,8 +1680,10 @@ func Test_AppPrepare(t *testing.T) {
},
},
{
name: "prepareAppQuery oidc app AssertIDTokenRole inactive",
prepare: prepareAppQuery,
name: "prepareAppQuery oidc app AssertIDTokenRole inactive",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{
sqlExpectations: mockQueries(
expectedAppQuery,
@ -1657,8 +1761,10 @@ func Test_AppPrepare(t *testing.T) {
},
},
{
name: "prepareAppQuery oidc app AssertIDTokenUserinfo inactive",
prepare: prepareAppQuery,
name: "prepareAppQuery oidc app AssertIDTokenUserinfo inactive",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{
sqlExpectations: mockQueries(
expectedAppQuery,
@ -1736,8 +1842,10 @@ func Test_AppPrepare(t *testing.T) {
},
},
{
name: "prepareAppQuery sql err",
prepare: prepareAppQuery,
name: "prepareAppQuery sql err",
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
return prepareAppQuery(ctx, db, false)
},
want: want{
sqlExpectations: mockQueryErr(
expectedAppQuery,

View File

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

View File

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

View File

@ -52,7 +52,7 @@ type IntrospectionClient struct {
//go:embed introspection_client_by_id.sql
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)
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
from config
join projections.apps7 apps on apps.id = config.app_id and apps.instance_id = config.instance_id
join projections.projects4 p on p.id = apps.project_id and p.instance_id = $1
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 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;

View File

@ -14,7 +14,7 @@ import (
"github.com/zitadel/zitadel/internal/database"
)
func TestQueries_GetIntrospectionClientByID(t *testing.T) {
func TestQueries_ActiveIntrospectionClientByID(t *testing.T) {
pubkeys := database.Map[[]byte]{
"key1": {1, 2, 3},
"key2": {4, 5, 6},
@ -96,7 +96,7 @@ func TestQueries_GetIntrospectionClientByID(t *testing.T) {
},
}
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)
assert.Equal(t, tt.want, got)
})

View File

@ -45,7 +45,7 @@ type OIDCClient struct {
//go:embed oidc_client_by_id.sql
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)
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.id_token_userinfo_assertion, c.clock_skew, c.additional_origins, a.project_id, p.project_role_assertion
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.projects4 p on p.id = a.project_id and p.instance_id = a.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 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
and c.client_id = $2
),

View File

@ -29,7 +29,7 @@ var (
testdataOidcClientNoSettings string
)
func TestQueries_GetOIDCClientByID(t *testing.T) {
func TestQueries_ActiveOIDCClientByID(t *testing.T) {
expQuery := regexp.QuoteMeta(oidcClientQuery)
cols := []string{"client"}
pubkey := `-----BEGIN RSA PUBLIC KEY-----
@ -232,7 +232,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx
},
}
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)
assert.Equal(t, tt.want, got)
})

View File

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

View File

@ -50,7 +50,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
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{}{
anyArg{},
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{}{
true,
"sender",
@ -100,7 +100,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
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{}{
anyArg{},
uint64(15),
@ -137,7 +137,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
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{}{
anyArg{},
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{}{
"sender",
"config-id",
@ -182,7 +182,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
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{}{
anyArg{},
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{}{
"endpoint",
"config-id",
@ -227,7 +227,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
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{}{
anyArg{},
uint64(15),
@ -264,7 +264,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
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{}{
anyArg{},
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{}{
"endpoint",
"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",
args: args{
@ -318,7 +381,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
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{}{
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{}{
"instance-id",
"config-id",
@ -374,7 +437,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
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{}{
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{}{
"instance-id",
"config-id",
@ -420,7 +483,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
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{}{
anyArg{},
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{}{
anyArg{},
uint64(15),
@ -465,7 +528,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
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{}{
anyArg{},
uint64(15),
@ -505,7 +568,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
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{}{
anyArg{},
"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{}{
anyArg{},
uint64(15),
@ -546,7 +609,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
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{}{
"config-id",
"instance-id",
@ -573,7 +636,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.smtp_configs3 WHERE (instance_id = $1)",
expectedStmt: "DELETE FROM projections.smtp_configs4 WHERE (instance_id = $1)",
expectedArgs: []interface{}{
"agg-id",
},

View File

@ -14,26 +14,26 @@ import (
)
var (
prepareSMTPConfigStmt = `SELECT projections.smtp_configs3.creation_date,` +
` projections.smtp_configs3.change_date,` +
` projections.smtp_configs3.resource_owner,` +
` projections.smtp_configs3.sequence,` +
` projections.smtp_configs3.id,` +
` projections.smtp_configs3.state,` +
` projections.smtp_configs3.description,` +
` projections.smtp_configs3_smtp.id,` +
` projections.smtp_configs3_smtp.tls,` +
` projections.smtp_configs3_smtp.sender_address,` +
` projections.smtp_configs3_smtp.sender_name,` +
` projections.smtp_configs3_smtp.reply_to_address,` +
` projections.smtp_configs3_smtp.host,` +
` projections.smtp_configs3_smtp.username,` +
` projections.smtp_configs3_smtp.password,` +
` projections.smtp_configs3_http.id,` +
` projections.smtp_configs3_http.endpoint` +
` FROM projections.smtp_configs3` +
` 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_configs3_http ON projections.smtp_configs3.id = projections.smtp_configs3_http.id AND projections.smtp_configs3.instance_id = projections.smtp_configs3_http.instance_id` +
prepareSMTPConfigStmt = `SELECT projections.smtp_configs4.creation_date,` +
` projections.smtp_configs4.change_date,` +
` projections.smtp_configs4.resource_owner,` +
` projections.smtp_configs4.sequence,` +
` projections.smtp_configs4.id,` +
` projections.smtp_configs4.state,` +
` projections.smtp_configs4.description,` +
` projections.smtp_configs4_smtp.id,` +
` projections.smtp_configs4_smtp.tls,` +
` projections.smtp_configs4_smtp.sender_address,` +
` projections.smtp_configs4_smtp.sender_name,` +
` projections.smtp_configs4_smtp.reply_to_address,` +
` projections.smtp_configs4_smtp.host,` +
` projections.smtp_configs4_smtp.username,` +
` projections.smtp_configs4_smtp.password,` +
` projections.smtp_configs4_http.id,` +
` projections.smtp_configs4_http.endpoint` +
` FROM projections.smtp_configs4` +
` 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_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'`
prepareSMTPConfigCols = []string{
"creation_date",

View File

@ -143,6 +143,10 @@ func NewUserGrantRoleQuery(value string) (SearchQuery, error) {
return NewTextQuery(UserGrantRoles, value, TextListContains)
}
func NewUserGrantStateQuery(value domain.UserGrantState) (SearchQuery, error) {
return NewNumberQuery(UserGrantState, value, NumberEquals)
}
func NewUserGrantWithGrantedQuery(owner string) (SearchQuery, error) {
orgQuery, err := NewUserGrantResourceOwnerSearchQuery(owner)
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")
}
latestState, err := q.latestState(ctx, userGrantTable)
latestSequence, err := q.latestState(ctx, userGrantTable)
if err != nil {
return nil, err
}
@ -290,7 +294,7 @@ func (q *Queries) UserGrants(ctx context.Context, queries *UserGrantsQueries, sh
return nil, err
}
grants.State = latestState
grants.State = latestSequence
return grants, nil
}

View File

@ -144,7 +144,7 @@ func (q *Queries) Memberships(ctx context.Context, queries *MembershipSearchQuer
if err != nil {
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 {
return nil, err
}
@ -157,7 +157,7 @@ func (q *Queries) Memberships(ctx context.Context, queries *MembershipSearchQuer
if err != nil {
return nil, err
}
memberships.State = latestState
memberships.State = latestSequence
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
from projections.users13 u
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 n.is_primary = true
),
@ -38,6 +38,7 @@ user_grants as (
where user_id = $1
and instance_id = $2
and project_id = any($3)
and state = 1
{{ if . -}}
and resource_owner = any($4)
{{- end }}

View File

@ -3,7 +3,6 @@ package database
import (
"time"
"github.com/shopspring/decimal"
"github.com/zitadel/logging"
"golang.org/x/exp/constraints"
)
@ -95,7 +94,7 @@ func (c numberCompare) String() string {
}
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
// constraints.Integer | constraints.Float | time.Time | placeholder
}

View File

@ -2,8 +2,6 @@ package eventstore
import (
"context"
"github.com/shopspring/decimal"
)
func NewEventstore(querier Querier, pusher Pusher) *EventStore {
@ -32,12 +30,12 @@ type healthier interface {
}
type GlobalPosition struct {
Position decimal.Decimal
Position float64
InPositionOrder uint32
}
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 {

View File

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

View File

@ -8,8 +8,6 @@ import (
"testing"
"time"
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/v2/database"
"github.com/zitadel/zitadel/internal/v2/database/mock"
"github.com/zitadel/zitadel/internal/v2/eventstore"
@ -543,13 +541,13 @@ func Test_writeFilter(t *testing.T) {
args: args{
filter: eventstore.NewFilter(
eventstore.FilterPagination(
eventstore.PositionGreater(decimal.NewFromFloat(123.4), 0),
eventstore.PositionGreater(123.4, 0),
),
),
},
want: wantQuery{
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{
filter: eventstore.NewFilter(
eventstore.FilterPagination(
// eventstore.PositionGreater(decimal.NewFromFloat(123.4), 0),
// eventstore.PositionGreater(123.4, 0),
// eventstore.PositionLess(125.4, 10),
eventstore.PositionBetween(
&eventstore.GlobalPosition{Position: decimal.NewFromFloat(123.4)},
&eventstore.GlobalPosition{Position: decimal.NewFromFloat(125.4), InPositionOrder: 10},
&eventstore.GlobalPosition{Position: 123.4},
&eventstore.GlobalPosition{Position: 125.4, InPositionOrder: 10},
),
),
),
},
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",
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
// 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)},
@ -579,13 +577,13 @@ func Test_writeFilter(t *testing.T) {
args: args{
filter: eventstore.NewFilter(
eventstore.FilterPagination(
eventstore.PositionGreater(decimal.NewFromFloat(123.4), 12),
eventstore.PositionGreater(123.4, 12),
),
),
},
want: wantQuery{
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.Limit(10),
eventstore.Offset(3),
eventstore.PositionGreater(decimal.NewFromFloat(123.4), 12),
eventstore.PositionGreater(123.4, 12),
),
),
},
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",
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.Limit(10),
eventstore.Offset(3),
eventstore.PositionGreater(decimal.NewFromFloat(123.4), 12),
eventstore.PositionGreater(123.4, 12),
),
eventstore.AppendAggregateFilter("user"),
),
},
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",
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.Limit(10),
eventstore.Offset(3),
eventstore.PositionGreater(decimal.NewFromFloat(123.4), 12),
eventstore.PositionGreater(123.4, 12),
),
eventstore.AppendAggregateFilter("user"),
eventstore.AppendAggregateFilter(
@ -639,7 +637,7 @@ func Test_writeFilter(t *testing.T) {
},
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",
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(
// 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(
@ -1067,9 +1065,9 @@ func Test_writeQueryUse_examples(t *testing.T) {
"instance",
"user",
"user.token.added",
decimal.NewFromInt(12),
float64(12),
uint32(4),
decimal.NewFromInt(12),
float64(12),
"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"},
@ -1203,7 +1201,7 @@ func Test_executeQuery(t *testing.T) {
time.Now(),
"event.type",
uint32(23),
decimal.NewFromInt(123).String(),
float64(123),
uint32(0),
nil,
"gigi",
@ -1237,7 +1235,7 @@ func Test_executeQuery(t *testing.T) {
time.Now(),
"event.type",
uint32(23),
decimal.NewFromInt(123).String(),
float64(123),
uint32(0),
[]byte(`{"name": "gigi"}`),
"gigi",
@ -1271,7 +1269,7 @@ func Test_executeQuery(t *testing.T) {
time.Now(),
"event.type",
uint32(23),
decimal.NewFromInt(123).String(),
float64(123),
uint32(0),
nil,
"gigi",
@ -1285,7 +1283,7 @@ func Test_executeQuery(t *testing.T) {
time.Now(),
"event.type",
uint32(24),
decimal.NewFromInt(124).String(),
float64(124),
uint32(0),
[]byte(`{"name": "gigi"}`),
"gigi",
@ -1319,7 +1317,7 @@ func Test_executeQuery(t *testing.T) {
time.Now(),
"event.type",
uint32(23),
decimal.NewFromInt(123).String(),
float64(123),
uint32(0),
nil,
"gigi",
@ -1333,7 +1331,7 @@ func Test_executeQuery(t *testing.T) {
time.Now(),
"event.type",
uint32(24),
decimal.NewFromInt(124).String(),
float64(124),
uint32(0),
[]byte(`{"name": "gigi"}`),
"gigi",

View File

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

View File

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

View File

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

View File

@ -1,8 +1,6 @@
package mirror
import (
"github.com/shopspring/decimal"
"github.com/zitadel/zitadel/internal/v2/eventstore"
"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 string `json:"source"`
// Position until data will be mirrored
Position decimal.Decimal `json:"position"`
Position float64 `json:"position"`
}
const SucceededType = eventTypePrefix + "succeeded"
@ -40,7 +38,7 @@ func SucceededEventFromStorage(event *eventstore.StorageEvent) (e *SucceededEven
}, nil
}
func NewSucceededCommand(source string, position decimal.Decimal) *eventstore.Command {
func NewSucceededCommand(source string, position float64) *eventstore.Command {
return &eventstore.Command{
Action: eventstore.Action[any]{
Creator: Creator,

View File

@ -4,43 +4,42 @@ ZITADEL_HOST ?=
ADMIN_LOGIN_NAME ?=
ADMIN_PASSWORD ?=
K6 := ./../../xk6-modules/k6
.PHONY: human_password_login
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
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
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
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
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
introspect: ensure_modules bundle
go install go.k6.io/xk6/cmd/xk6@latest
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
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
machine_jwt_profile_grant: ensure_modules ensure_key_pair bundle
go install go.k6.io/xk6/cmd/xk6@latest
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
machine_jwt_profile_grant_single_user: ensure_modules ensure_key_pair bundle
@ -65,8 +64,6 @@ endif
bundle:
npm i
npm run bundle
go install go.k6.io/xk6/cmd/xk6@latest
cd ../../xk6-modules && xk6 build --with xk6-zitadel=.
.PHONY: ensure_key_pair
ensure_key_pair:

View File

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

View File

@ -437,7 +437,7 @@ service AdminService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP";
tags: "SMTP Configs";
deprecated: true;
};
}
@ -457,7 +457,7 @@ service AdminService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP";
tags: "SMTP Configs";
deprecated: true;
};
}
@ -478,7 +478,7 @@ service AdminService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP";
tags: "SMTP Configs";
deprecated: true;
};
}
@ -499,7 +499,7 @@ service AdminService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP";
tags: "SMTP Configs";
deprecated: true;
};
}
@ -520,7 +520,7 @@ service AdminService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP";
tags: "SMTP Configs";
deprecated: true;
};
}
@ -541,7 +541,7 @@ service AdminService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP Provider";
tags: "SMTP Configs";
summary: "Activate SMTP Provider";
description: "Activate an SMTP provider."
deprecated: true;
@ -564,7 +564,7 @@ service AdminService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP Provider";
tags: "SMTP Configs";
deprecated: true;
};
}
@ -584,7 +584,7 @@ service AdminService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP";
tags: "SMTP Configs";
deprecated: true;
};
}
@ -605,7 +605,7 @@ service AdminService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP Provider";
tags: "SMTP Configs";
deprecated: true;
};
}
@ -626,7 +626,7 @@ service AdminService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP Provider";
tags: "SMTP Configs";
deprecated: true;
};
}
@ -663,7 +663,7 @@ service AdminService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Email providers";
tags: "Email Provider";
summary: "List Email providers";
description: "Returns a list of Email providers."
};
@ -679,7 +679,7 @@ service AdminService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Email";
tags: "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."
};
@ -695,7 +695,7 @@ service AdminService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Email";
tags: "Email Provider";
summary: "Get 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) = {
tags: "Email";
tags: "Email Provider";
summary: "Add SMTP Email provider";
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) = {
tags: "Email";
tags: "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."
};
@ -746,7 +746,7 @@ service AdminService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Email";
tags: "Email Provider";
summary: "Add HTTP Email provider";
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) = {
tags: "Email";
tags: "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."
};
@ -780,7 +780,7 @@ service AdminService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP";
tags: "Email Provider";
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."
};
@ -830,7 +830,7 @@ service AdminService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Email";
tags: "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."
};
@ -847,7 +847,7 @@ service AdminService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP Email Provider";
tags: "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."
};
@ -864,7 +864,7 @@ service AdminService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMTP EMailProvider";
tags: "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."
};

View File

@ -1565,6 +1565,11 @@ message UserGrant {
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 {