Merge remote-tracking branch 'origin/main' into user-v3-authenticator

This commit is contained in:
Stefan Benz 2024-09-25 16:55:39 +02:00
commit 82b87b5571
No known key found for this signature in database
GPG Key ID: 071AA751ED4F9D31
31 changed files with 1051 additions and 284 deletions

View File

@ -3,6 +3,34 @@
Dear community!
We're excited to announce bi-weekly office hours.
## #5 Q&A
Dear community,
This week's office hour is dedicated for you to drop by and ask any questions you may have about ZITADEL. We are happy to discuss anything, from Actions to Zero downtime deployments.
Join us on the stage or ask your questions in the chat next Wednesday in the office hours channel on Discord. We're looking forward to have a nice chat with you.
**What to expect:**
* **Q&A Session**: Ask your questions and feel free to join the discussion to help others getting their questions answered
**Details:**
* **Target Audience:** Developers and IT Ops personnel using ZITADEL
* **Topic:** Q\&A session
* **When**: Wednesday 25th of September 6 pm UTC
* **Duration**: about 1 hour
* **Platform:** Zitadel Discord Server (Join us here: https://discord.gg/zitadel-927474939156643850?event=1286221582838272000 )
**In the meantime:**
If you have questions upfront, feel free to already post them in the chat of the [office hours channel](https://zitadel.com/office-hours) on our Discord server :gigi:
We look forward to seeing you there\!
**P.S.** Spread the word\! Share this announcement with your fellow ZITADEL users who might be interested 📢
## #4 Login UI deepdive
Dear community,

View File

@ -28,7 +28,7 @@
"@fortawesome/angular-fontawesome": "^0.13.0",
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-brands-svg-icons": "^6.4.2",
"@grpc/grpc-js": "^1.11.1",
"@grpc/grpc-js": "^1.11.2",
"@netlify/framework-info": "^9.8.13",
"@ngx-translate/core": "^15.0.0",
"angular-oauth2-oidc": "^15.0.1",
@ -42,14 +42,14 @@
"google-protobuf": "^3.21.2",
"grpc-web": "^1.4.1",
"i18n-iso-countries": "^7.7.0",
"libphonenumber-js": "^1.11.4",
"libphonenumber-js": "^1.11.8",
"material-design-icons-iconfont": "^6.1.1",
"moment": "^2.29.4",
"ngx-color": "^9.0.0",
"opentype.js": "^1.3.4",
"rxjs": "~7.8.0",
"tinycolor2": "^1.6.0",
"tslib": "^2.6.2",
"tslib": "^2.7.0",
"uuid": "^10.0.0",
"zone.js": "~0.13.3"
},
@ -60,16 +60,16 @@
"@angular-eslint/eslint-plugin-template": "18.0.0",
"@angular-eslint/schematics": "16.2.0",
"@angular-eslint/template-parser": "18.3.0",
"@angular/cli": "^16.2.14",
"@angular/cli": "^16.2.15",
"@angular/compiler-cli": "^16.2.5",
"@angular/language-service": "^18.2.2",
"@bufbuild/buf": "^1.39.0",
"@angular/language-service": "^18.2.4",
"@bufbuild/buf": "^1.41.0",
"@types/file-saver": "^2.0.7",
"@types/google-protobuf": "^3.15.3",
"@types/jasmine": "~5.1.4",
"@types/jasminewd2": "~2.0.13",
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "^22.5.2",
"@types/node": "^22.5.5",
"@types/opentype.js": "^1.3.8",
"@types/qrcode": "^1.5.2",
"@types/uuid": "^10.0.0",
@ -77,7 +77,7 @@
"@typescript-eslint/parser": "^5.60.1",
"codelyzer": "^6.0.2",
"eslint": "^8.50.0",
"jasmine-core": "~5.2.0",
"jasmine-core": "~5.3.0",
"jasmine-spec-reporter": "~7.0.0",
"karma": "^6.4.2",
"karma-chrome-launcher": "^3.2.0",

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

@ -26,6 +26,14 @@
"@angular-devkit/core" "16.2.14"
rxjs "7.8.1"
"@angular-devkit/architect@0.1602.15":
version "0.1602.15"
resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1602.15.tgz#b70f2456677f6859d4dac4ad80c6b13d00108797"
integrity sha512-+yPlUG5c8l7Z/A6dyeV7NQjj4WDWnWWQt+8eW/KInwVwoYiM32ntTJ0M4uU/aDdHuwKQnMLly28AcSWPWKYf2Q==
dependencies:
"@angular-devkit/core" "16.2.15"
rxjs "7.8.1"
"@angular-devkit/build-angular@^16.2.2":
version "16.2.14"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-16.2.14.tgz#0c4e41aa3f67e52b474b2fabeb027aebf6e76566"
@ -118,12 +126,24 @@
rxjs "7.8.1"
source-map "0.7.4"
"@angular-devkit/schematics@16.2.14":
version "16.2.14"
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-16.2.14.tgz#819c2ef8bb298e383cb312d9d1411f5970f0328f"
integrity sha512-B6LQKInCT8w5zx5Pbroext5eFFRTCJdTwHN8GhcVS8IeKCnkeqVTQLjB4lBUg7LEm8Y7UHXwzrVxmk+f+MBXhw==
"@angular-devkit/core@16.2.15":
version "16.2.15"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-16.2.15.tgz#44ef98cda82ef82435a2a41507f8c24720d372df"
integrity sha512-68BgPWpcjNKz++uvLFG8IZaOH3ti2BWQVqaE3yTIYaMoNt0y0A0X2MUVd7EGbAGUk2JdloWJv5LTPVZMzCuK4w==
dependencies:
"@angular-devkit/core" "16.2.14"
ajv "8.12.0"
ajv-formats "2.1.1"
jsonc-parser "3.2.0"
picomatch "2.3.1"
rxjs "7.8.1"
source-map "0.7.4"
"@angular-devkit/schematics@16.2.15":
version "16.2.15"
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-16.2.15.tgz#cedcb48fdd240db0a779674cf52455a78a4098bb"
integrity sha512-C/j2EwapdBMf1HWDuH89bA9B2e511iEYImkyZ+vCSXRwGiWUaZCrhl18bvztpErTrdOLM3mCwNXWEAMXI4zUXA==
dependencies:
"@angular-devkit/core" "16.2.15"
jsonc-parser "3.2.0"
magic-string "0.30.1"
ora "5.4.1"
@ -242,15 +262,15 @@
optionalDependencies:
parse5 "^7.1.2"
"@angular/cli@^16.2.14":
version "16.2.14"
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-16.2.14.tgz#ab58910ae354ee31b89a7479efd5978fd1a3042e"
integrity sha512-0y71jtitigVolm4Rim1b8xPQ+B22cGp4Spef2Wunpqj67UowN6tsZaVuWBEQh4u5xauX8LAHKqsvy37ZPWCc4A==
"@angular/cli@^16.2.15":
version "16.2.15"
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-16.2.15.tgz#951d84ef9a7113242b10fe89be1adfa3a94dd6aa"
integrity sha512-nNUmt0ZRj2xHH8tGXSJUiusP5rmakAz0f6cc6T4p03OyeShOKdvs9+/F4hzzsM79/ylZofBlFfwYVCBTbOtMqw==
dependencies:
"@angular-devkit/architect" "0.1602.14"
"@angular-devkit/core" "16.2.14"
"@angular-devkit/schematics" "16.2.14"
"@schematics/angular" "16.2.14"
"@angular-devkit/architect" "0.1602.15"
"@angular-devkit/core" "16.2.15"
"@angular-devkit/schematics" "16.2.15"
"@schematics/angular" "16.2.15"
"@yarnpkg/lockfile" "1.1.0"
ansi-colors "4.1.3"
ini "4.1.1"
@ -318,10 +338,10 @@
dependencies:
tslib "^2.3.0"
"@angular/language-service@^18.2.2":
version "18.2.2"
resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-18.2.2.tgz#8a6b3f224871cb4b1dd5d76a43a1c3884d14aa62"
integrity sha512-aROQNQeLf+o+F5OVvE/9BUe/Tpv8pjzmrZlogBbic5cb4IqSNhR4RjxbgIyXBO/6bhLCZwqfmMqRbW2J2xqMkg==
"@angular/language-service@^18.2.4":
version "18.2.4"
resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-18.2.4.tgz#c449a75bc405bf519fc90f7a9269a98e2a1f7758"
integrity sha512-Keg6n8u8xHLhRDTmx4hUqh1AtVFUt8hDxPMYSUu64czjOT5Dnh8XsgKagu563NEjxbDaCzttPuO+y3DlcaDZoQ==
"@angular/material-moment-adapter@^16.2.4":
version "16.2.14"
@ -1462,47 +1482,47 @@
"@babel/helper-validator-identifier" "^7.24.7"
to-fast-properties "^2.0.0"
"@bufbuild/buf-darwin-arm64@1.39.0":
version "1.39.0"
resolved "https://registry.yarnpkg.com/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.39.0.tgz#0ab8453dc7fc7694e5bd39c69d934edc51b81c81"
integrity sha512-Ptl0uAGssLxQTzoZhGwv1FFTbzUfcstIpEwMhN+XrwiuqsSxOg9eq/n3yXoci5VJsHokjDUHnWkR3y+j5P/5KA==
"@bufbuild/buf-darwin-arm64@1.41.0":
version "1.41.0"
resolved "https://registry.yarnpkg.com/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.41.0.tgz#a6aee96452f5a624eb7e5b0336833fdd7a3a7911"
integrity sha512-+G5DwpIgnm0AkqgxORxoYXVT0RGDcw8P4SXFXcovgvDBkk9rPvEI1dbPF83n3SUxzcu2A2OxC7DxlXszWIh2Gw==
"@bufbuild/buf-darwin-x64@1.39.0":
version "1.39.0"
resolved "https://registry.yarnpkg.com/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.39.0.tgz#9c9a211c8039b8cb89b45bf44f338edf82d5e506"
integrity sha512-XNCuy9sjQwVJ4NIZqxaTIyzUtlyquSkp/Uuoh5W5thJ3nzZ5RSgvXKF5iXHhZmesrfRGApktwoCx5Am8runsfQ==
"@bufbuild/buf-darwin-x64@1.41.0":
version "1.41.0"
resolved "https://registry.yarnpkg.com/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.41.0.tgz#aac6a6b86f6d1f30c86f70e918d212e067e5257f"
integrity sha512-qjkJ/LAWqNk3HX65n+JTt18WtKrhrrAhIu3Dpfbe0eujsxafFZKoPzlWJYybxvsaF9CdEyMMm/OalBPpoosMOA==
"@bufbuild/buf-linux-aarch64@1.39.0":
version "1.39.0"
resolved "https://registry.yarnpkg.com/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.39.0.tgz#9778732efbdbbfe02ec821017cc2392ce4a0153f"
integrity sha512-Am+hrw94awp/eY027ROXwRQBuwAzOpQ/4zI4dgmgsyhzeWZ8w1LWC8z2SSr8T2cqd0cm52KxtoWMW+B3b2qzbw==
"@bufbuild/buf-linux-aarch64@1.41.0":
version "1.41.0"
resolved "https://registry.yarnpkg.com/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.41.0.tgz#8ac97e7a19cf0c0957ca1b3e690d8c039b0b3468"
integrity sha512-5E+MLAF4QHPwAjwVVRRP3Is2U3zpIpQQR7S3di9HlKACbgvefJEBrUfRqQZvHrMuuynQRqjFuZD16Sfvxn9rCQ==
"@bufbuild/buf-linux-x64@1.39.0":
version "1.39.0"
resolved "https://registry.yarnpkg.com/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.39.0.tgz#d7ca62c4f506c60011f5a97ca2e8683aa26693b0"
integrity sha512-JXVkHoMrTvmpseqdoQPJJ6MRV7/vlloYtvXHHACEzVytYjljOYCNoVET/E5gLBco/edeXFMNc40cCi1KgL3rSw==
"@bufbuild/buf-linux-x64@1.41.0":
version "1.41.0"
resolved "https://registry.yarnpkg.com/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.41.0.tgz#8a272846929215affccb9c271f02948e10f8d4a9"
integrity sha512-W4T+uqmdtypzzatv6OXjUzGacZiNzGECogr+qDkJF38MSZd3jHXhTEN2KhRckl3i9rRAnfHBwG68BjCTxxBCOQ==
"@bufbuild/buf-win32-arm64@1.39.0":
version "1.39.0"
resolved "https://registry.yarnpkg.com/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.39.0.tgz#efdaf1eca30445f04124c6d829a46a676e6b1dc3"
integrity sha512-akdGW02mo04wbLfjNMBQqxC4mPQ/L/vTU8/o79I67GSxyFYt7bKifvYIYhAA39C2gibHyB7ZLmoeRPbaU8wbYA==
"@bufbuild/buf-win32-arm64@1.41.0":
version "1.41.0"
resolved "https://registry.yarnpkg.com/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.41.0.tgz#e26b67b2da15e284326c3d3c38255b443e201e0b"
integrity sha512-OsRVoTZHJZYGIphAwaRqcCeYR9Sk5VEMjpCJiFt/dkHxx2acKH4u/7O+633gcCxQL8EnsU2l8AfdbW7sQaOvlg==
"@bufbuild/buf-win32-x64@1.39.0":
version "1.39.0"
resolved "https://registry.yarnpkg.com/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.39.0.tgz#09f2b0290818d826847689d6149f8fb0def4ac4b"
integrity sha512-jos08UMg9iUZsGjPrNpLXP+FNk6q6GizO+bjee/GcI0kSijIzXYMg14goQr0TKlvqs/+IRAM5vZIokQBYlAENQ==
"@bufbuild/buf-win32-x64@1.41.0":
version "1.41.0"
resolved "https://registry.yarnpkg.com/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.41.0.tgz#b2ff4e9cdb9f73baaad216d35c91c733c4c4b661"
integrity sha512-2KJLp7Py0GsfRjDxwBzS17RMpaYFGCvzkwY5CtxfPMw8cg6cE7E36r+vcjHh5dBOj/CumaiXLTwxhCSBtp0V1g==
"@bufbuild/buf@^1.39.0":
version "1.39.0"
resolved "https://registry.yarnpkg.com/@bufbuild/buf/-/buf-1.39.0.tgz#65884f55d072b93122959c92b389c1d7d8ab510b"
integrity sha512-lm7xb9pc7X04rRjCQ69o9byAAZ7/dsUQGoH+iJ9uBSXQWiwQ1Ts8gneBnuUVsAH2vdW73NFBpmNQGE9XtFauVQ==
"@bufbuild/buf@^1.41.0":
version "1.41.0"
resolved "https://registry.yarnpkg.com/@bufbuild/buf/-/buf-1.41.0.tgz#76077338696009c2f34e7ca1c76baf89a04079f5"
integrity sha512-6pN2fqMrPqnIkrC1q9KpXpu7fv3Rul0ZPhT4MSYYj+8VcyR3kbLVk6K+CzzPvYhr4itfotnI3ZVGQ/X/vupECg==
optionalDependencies:
"@bufbuild/buf-darwin-arm64" "1.39.0"
"@bufbuild/buf-darwin-x64" "1.39.0"
"@bufbuild/buf-linux-aarch64" "1.39.0"
"@bufbuild/buf-linux-x64" "1.39.0"
"@bufbuild/buf-win32-arm64" "1.39.0"
"@bufbuild/buf-win32-x64" "1.39.0"
"@bufbuild/buf-darwin-arm64" "1.41.0"
"@bufbuild/buf-darwin-x64" "1.41.0"
"@bufbuild/buf-linux-aarch64" "1.41.0"
"@bufbuild/buf-linux-x64" "1.41.0"
"@bufbuild/buf-win32-arm64" "1.41.0"
"@bufbuild/buf-win32-x64" "1.41.0"
"@colors/colors@1.5.0":
version "1.5.0"
@ -1810,10 +1830,10 @@
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==
"@grpc/grpc-js@^1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.11.1.tgz#a92f33e98f1959feffcd1b25a33b113d2c977b70"
integrity sha512-gyt/WayZrVPH2w/UTLansS7F9Nwld472JxxaETamrM8HNlsa+jSLNyKAZmhxI2Me4c3mQHFiS1wWHDY1g1Kthw==
"@grpc/grpc-js@^1.11.2":
version "1.11.2"
resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.11.2.tgz#541a00303e533b5efe9d84ed61b84cdf9dd93ee7"
integrity sha512-DWp92gDD7/Qkj7r8kus6/HCINeo3yPZWZ3paKgDgsbKbSpoxKg1yvN8xe2Q8uE3zOsPe3bX8FQX2+XValq2yTw==
dependencies:
"@grpc/proto-loader" "^0.7.13"
"@js-sdsl/ordered-map" "^4.4.2"
@ -2884,13 +2904,13 @@
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
"@schematics/angular@16.2.14":
version "16.2.14"
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-16.2.14.tgz#3aac7e05b6e3919195275cf06ac403d7a3567876"
integrity sha512-YqIv727l9Qze8/OL6H9mBHc2jVXzAGRNBYnxYWqWhLbfvuVbbldo6NNIIjgv6lrl2LJSdPAAMNOD5m/f6210ug==
"@schematics/angular@16.2.15":
version "16.2.15"
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-16.2.15.tgz#f3b810842959808f0d65ce816f4f0c1a7463c176"
integrity sha512-T7wEGYxidpLAkis+hO5nsVfnWsy6sXf1T9GS8uztC8IYYsnqB9jTVfjVyfhASugZasdmx7+jWv3oCGy6Z5ZehA==
dependencies:
"@angular-devkit/core" "16.2.14"
"@angular-devkit/schematics" "16.2.14"
"@angular-devkit/core" "16.2.15"
"@angular-devkit/schematics" "16.2.15"
jsonc-parser "3.2.0"
"@sigstore/bundle@^1.1.0":
@ -3098,10 +3118,10 @@
dependencies:
"@types/node" "*"
"@types/node@*", "@types/node@>=10.0.0", "@types/node@>=13.7.0", "@types/node@^22.5.2":
version "22.5.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.2.tgz#e42344429702e69e28c839a7e16a8262a8086793"
integrity sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==
"@types/node@*", "@types/node@>=10.0.0", "@types/node@>=13.7.0", "@types/node@^22.5.5":
version "22.5.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.5.tgz#52f939dd0f65fc552a4ad0b392f3c466cc5d7a44"
integrity sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==
dependencies:
undici-types "~6.19.2"
@ -3978,10 +3998,10 @@ blocking-proxy@^1.0.0:
dependencies:
minimist "^1.2.0"
body-parser@1.20.2, body-parser@^1.19.0:
version "1.20.2"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
body-parser@1.20.3, body-parser@^1.19.0:
version "1.20.3"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6"
integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==
dependencies:
bytes "3.1.2"
content-type "~1.0.5"
@ -3991,7 +4011,7 @@ body-parser@1.20.2, body-parser@^1.19.0:
http-errors "2.0.0"
iconv-lite "0.4.24"
on-finished "2.4.1"
qs "6.11.0"
qs "6.13.0"
raw-body "2.5.2"
type-is "~1.6.18"
unpipe "1.0.0"
@ -4902,6 +4922,11 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
encodeurl@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
encoding@^0.1.13:
version "0.1.13"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
@ -5276,36 +5301,36 @@ exponential-backoff@^3.1.1:
integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==
express@^4.17.3:
version "4.19.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==
version "4.20.0"
resolved "https://registry.yarnpkg.com/express/-/express-4.20.0.tgz#f1d08e591fcec770c07be4767af8eb9bcfd67c48"
integrity sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"
body-parser "1.20.2"
body-parser "1.20.3"
content-disposition "0.5.4"
content-type "~1.0.4"
cookie "0.6.0"
cookie-signature "1.0.6"
debug "2.6.9"
depd "2.0.0"
encodeurl "~1.0.2"
encodeurl "~2.0.0"
escape-html "~1.0.3"
etag "~1.8.1"
finalhandler "1.2.0"
fresh "0.5.2"
http-errors "2.0.0"
merge-descriptors "1.0.1"
merge-descriptors "1.0.3"
methods "~1.1.2"
on-finished "2.4.1"
parseurl "~1.3.3"
path-to-regexp "0.1.7"
path-to-regexp "0.1.10"
proxy-addr "~2.0.7"
qs "6.11.0"
range-parser "~1.2.1"
safe-buffer "5.2.1"
send "0.18.0"
serve-static "1.15.0"
send "0.19.0"
serve-static "1.16.0"
setprototypeof "1.2.0"
statuses "2.0.1"
type-is "~1.6.18"
@ -6482,10 +6507,10 @@ jasmine-core@~2.8.0:
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e"
integrity sha512-SNkOkS+/jMZvLhuSx1fjhcNWUC/KG6oVyFUGkSBEr9n1axSNduWU8GlI7suaHXr4yxjet6KjrUZxUTE5WzzWwQ==
jasmine-core@~5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-5.2.0.tgz#7d0aa4c26cb3dbaed201d0505489baf1e48faeca"
integrity sha512-tSAtdrvWybZkQmmaIoDgnvHG8ORUNw5kEVlO5CvrXj02Jjr9TZrmjFq7FUiOUzJiOP2wLGYT6PgrQgQF4R1xiw==
jasmine-core@~5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-5.3.0.tgz#ed784e5a10af43988d8408bad80b9f08e240a3f8"
integrity sha512-zsOmeBKESky4toybvWEikRiZ0jHoBEu79wNArLfMdSnlLMZx3Xcp6CSm2sUcYyoJC+Uyj8LBJap/MUbVSfJ27g==
jasmine-spec-reporter@~7.0.0:
version "7.0.0"
@ -6810,10 +6835,10 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"
libphonenumber-js@^1.11.4:
version "1.11.5"
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.5.tgz#50a441da5ff9ed9a322d796a14f1e9cbc0fdabdf"
integrity sha512-TwHR5BZxGRODtAfz03szucAkjT5OArXr+94SMtAM2pYXIlQNVMrxvb6uSCbnaJJV6QXEyICk7+l6QPgn72WHhg==
libphonenumber-js@^1.11.8:
version "1.11.8"
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.8.tgz#697fdd36500a97bc672d7927d867edf34b4bd2a7"
integrity sha512-0fv/YKpJBAgXKy0kaS3fnqoUVN8901vUYAKIGD/MWZaDfhJt1nZjPL3ZzdZBt/G8G8Hw2J1xOIrXWdNHFHPAvg==
license-webpack-plugin@4.0.2:
version "4.0.2"
@ -7034,10 +7059,10 @@ memfs@^3.4.12, memfs@^3.4.3:
dependencies:
fs-monkey "^1.0.4"
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
merge-descriptors@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
merge-stream@^2.0.0:
version "2.0.0"
@ -7855,10 +7880,10 @@ path-scurry@^1.11.1:
lru-cache "^10.2.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
path-to-regexp@0.1.10:
version "0.1.10"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b"
integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==
path-type@^4.0.0:
version "4.0.0"
@ -8145,6 +8170,13 @@ qs@6.11.0:
dependencies:
side-channel "^1.0.4"
qs@6.13.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
dependencies:
side-channel "^1.0.6"
qs@~6.5.2:
version "6.5.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
@ -8618,6 +8650,25 @@ send@0.18.0:
range-parser "~1.2.1"
statuses "2.0.1"
send@0.19.0:
version "0.19.0"
resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8"
integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
dependencies:
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
fresh "0.5.2"
http-errors "2.0.0"
mime "1.6.0"
ms "2.1.3"
on-finished "2.4.1"
range-parser "~1.2.1"
statuses "2.0.1"
serialize-javascript@^6.0.0, serialize-javascript@^6.0.1:
version "6.0.2"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2"
@ -8638,10 +8689,10 @@ serve-index@^1.9.1:
mime-types "~2.1.17"
parseurl "~1.3.2"
serve-static@1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
serve-static@1.16.0:
version "1.16.0"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.0.tgz#2bf4ed49f8af311b519c46f272bf6ac3baf38a92"
integrity sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==
dependencies:
encodeurl "~1.0.2"
escape-html "~1.0.3"
@ -8704,7 +8755,7 @@ shell-quote@^1.8.1:
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680"
integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==
side-channel@^1.0.4:
side-channel@^1.0.4, side-channel@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==
@ -8956,7 +9007,16 @@ streamroller@^3.1.5:
debug "^4.3.4"
fs-extra "^8.1.0"
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -8993,7 +9053,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@ -9007,6 +9067,13 @@ strip-ansi@^3.0.0:
dependencies:
ansi-regex "^2.0.0"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^7.0.1:
version "7.1.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@ -9269,10 +9336,10 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.4.1, tslib@^2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==
tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.4.1, tslib@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
tsutils@^3.21.0:
version "3.21.0"
@ -9781,7 +9848,7 @@ word-wrap@^1.2.5:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@ -9799,6 +9866,15 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"

View File

@ -1 +0,0 @@
You have to install Pylon as described in [their documentation](https://pylon.cronit.io/docs/installation/).

View File

@ -6,7 +6,6 @@ sidebar_label: Pylon
import AppJWT from "../imports/_app_jwt.mdx";
import ServiceuserJWT from "../imports/_serviceuser_jwt.mdx";
import ServiceuserRole from "../imports/_serviceuser_role.mdx";
import SetupPylon from "../imports/_setup_pylon.mdx";
This integration guide demonstrates the recommended way to incorporate ZITADEL into your [Pylon](https://pylon.cronit.io) service.
It explains how to check the token validity in the API and how to check for permissions.
@ -43,26 +42,27 @@ And the following from the Serviceuser:
## Setup new Pylon service
### Setup Pylon
Pylon allows you to create a new service using the `npm create pylon` command. This command creates a new Pylon project with a basic project structure and configuration.
During the setup process, you can choose your preferred runtime, such as Bun, Node.js, or Cloudflare Workers.
<SetupPylon />
**This guide uses the Bun runtime.**
### Creating a new project
To create a new Pylon project, run the following command:
```bash
pylon new my-pylon-project
npm create pylon my-pylon@latest
```
This will create a new directory called `my-pylon-project` with a basic Pylon project structure.
This will create a new directory called `my-pylon` with a basic Pylon project structure.
### Project structure
Pylon projects are structured as follows:
```
my-pylon-project/
my-pylon/
├── .pylon/
├── src/
│ ├── index.ts
@ -81,16 +81,18 @@ my-pylon-project/
Here's an example of a basic Pylon service:
```ts
import { defineService } from "@getcronit/pylon";
import { app } from "@getcronit/pylon";
export default defineService({
export const graphql = {
Query: {
sum: (a: number, b: number) => a + b,
},
Mutation: {
divide: (a: number, b: number) => a / b,
},
});
};
export default app;
```
## Secure the API
@ -113,6 +115,8 @@ AUTH_PROJECT_ID='250719519163548112'
2. Copy the `.json`-key-file that you downloaded from the ZITADEL Console into the root folder of your project and rename it to `key.json`.
3. (Optional) For added convenience in production environments, you can include the content of the .json key file as `AUTH_KEY` in the .env file or as an environment variable.
### Auth
Pylon provides a auth module and a decorator to check the validity of the token and the permissions.
@ -140,8 +144,7 @@ The following code demonstrates how to create a Pylon service with the required
```ts
import {
defineService,
PylonAPI,
app,
auth,
requireAuth,
getContext,
@ -208,7 +211,7 @@ class User {
}
}
export default defineService({
export const graphql = {
Query: {
me: User.me,
info: () => "Public Data",
@ -216,43 +219,43 @@ export default defineService({
Mutation: {
createUser: User.create,
},
};
// Initialize the authentication middleware
app.use("*", auth.initialize());
// Automatically try to create a user for each request for demonstration purposes
app.use(async (_, next) => {
try {
await User.create();
} catch {
// Ignore errors
// Fail silently if the user already exists
}
await next();
});
export const configureApp: PylonAPI["configureApp"] = (app) => {
// Initialize the authentication middleware
app.use("*", auth.initialize());
app.get("/api/info", (c) => {
return new Response("Public Data");
});
// Automatically try to create a user for each request for demonstration purposes
app.use(async (_, next) => {
try {
await User.create();
} catch {
// Ignore errors
// Fail silently if the user already exists
}
// The `auth.require()` middleware is optional here, as the `User.me` method already checks for it.
app.get("/api/me", auth.require(), async (c) => {
const user = await User.me();
await next();
});
return c.json(user);
});
app.get("/api/info", (c) => {
return new Response("Public Data");
});
// A role check for `read:messages` is not required here, as the `user.messages` method already checks for it.
app.get("/api/me/messages", auth.require(), async (c) => {
const user = await User.me();
// The `auth.require()` middleware is optional here, as the `User.me` method already checks for it.
app.get("/api/me", auth.require(), async (c) => {
const user = await User.me();
// This will throw an error if the user does not have the `read:messages` role
return c.json(await user.messages());
});
return c.json(user);
});
// A role check for `read:messages` is not required here, as the `user.messages` method already checks for it.
app.get("/api/me/messages", auth.require(), async (c) => {
const user = await User.me();
// This will throw an error if the user does not have the `read:messages` role
return c.json(await user.messages());
});
};
export default app;
```
### Call the API
@ -273,7 +276,7 @@ export TOKEN='MtjHodGy4zxKylDOhg6kW90WeEQs2q...'
Now you have to start the Pylon service:
```bash
bun run develop
bun run dev
```
With the access token, you can then do the following calls:

View File

@ -0,0 +1,166 @@
---
title: SMS, SMTP and HTTP Provider for Notifications
sidebar_label: Notification Providers
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
ZITADEL can send messages to users via different notification providers, such as SMS, SMTP, or Webhook (HTTP Provider).
While you can add multiple providers to different channels, messages will only be delivered via the actived provider.
Message and notification texts can be [customized](./texts) for an instance or for each organization.
## SMS providers
ZITADEL integrates with Twilio as SMS provider.
## SMTP providers
Integration with most SMTP providers is possible through a generic SMTP provider template that allows you to configure custom SMTP providers.
Additionally, integration templates are available for:
- Amazon SES
- Mailgun
- Mailjet
- Postmark
- Sendgrid
:::info Default Provider ZITADEL Cloud
A default SMTP provider is configured for ZITADEL Cloud customers.
This provider meant for development and testing purposes and you must replace this provider with your custom SMTP provider for production use cases to guarantee security and reliability of your service.
:::
## Webhook / HTTP provider
Webhook (HTTP Provider) allows you to fully customize the messages and use integrate with any provider or custom solution to deliver the messages to users.
A provider with HTTP type will send the messages and the data to a pre-defined webhook as JSON.
### Configuring a HTTP provider
<Tabs>
<TabItem value="sms" label="SMS" default>
First [add a new SMS Provider of type HTTP](/apis/resources/admin/admin-service-add-sms-provider-http) to create a new HTTP provider that can be used to send SMS messages:
```bash
curl -L 'https://$CUSTOM-DOMAIN/admin/v1/sms/http' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
-d '{
"endpoint": "http://relay.example.com/provider",
"description": "provider description"
}'
```
Where `endpoint` defines the Webhook endpoint to which the data should be sent to.
The result will contain an ID of the provider that we need in the next step.
You can configure multiple SMS providers at the same time.
To use the HTTP provider you need to [activate the SMS provider](/apis/resources/admin/admin-service-activate-sms-provider):
```bash
curl -L 'https://$CUSTOM-DOMAIN/admin/v1/sms/:id/_activate' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
-d '{}'
```
The `id` is the provider's ID from the previous step.
See full API reference for [SMS Providers](/apis/resources/admin/sms-provider) for more details.
</TabItem>
<TabItem value="email" label="Email">
First [add a new Email Provider of type HTTP](/apis/resources/admin/admin-service-add-email-provider-http) to create a new HTTP provider that can be used to send SMS messages:
```bash
curl -L 'https://$CUSTOM-DOMAIN/admin/v1/email/http' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
-d '{
"endpoint": "http://relay.example.com/provider",
"description": "provider description"
}'
```
Where `endpoint` defines the Webhook endpoint to which the data should be sent to.
The result will contain an ID of the provider that we need in the next step.
You can configure multiple Email providers at the same time.
To use the HTTP provider you need to [activate the Email provider](/apis/resources/admin/admin-service-activate-email-provider):
```bash
curl -L 'https://$CUSTOM-DOMAIN/admin/v1/email/:id/_activate' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
-d '{}'
```
The `id` is the provider's ID from the previous step.
See full API reference for [Email Providers](/apis/resources/admin/admin-service-list-email-providers) for more details.
</TabItem>
</Tabs>
### HTTP provider payload
In case of the Twilio and Email 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 payload sent via Email 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

@ -1,74 +0,0 @@
---
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

@ -187,7 +187,7 @@ module.exports = {
selector: "div#",
},
prism: {
additionalLanguages: ["csharp", "dart", "groovy", "regex", "java", "php", "python", "protobuf"],
additionalLanguages: ["csharp", "dart", "groovy", "regex", "java", "php", "python", "protobuf", "json", "bash"],
},
colorMode: {
defaultMode: "dark",

View File

@ -154,11 +154,10 @@ module.exports = {
type: "category",
label: "Customize",
items: [
"guides/manage/customize/branding",
"guides/manage/customize/texts",
"guides/manage/customize/behavior",
"guides/manage/customize/restrictions",
"guides/manage/customize/notifications",
{
type: "autogenerated",
dirName: "guides/manage/customize",
},
],
},
{

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

@ -330,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 {
@ -799,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

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

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

View File

@ -131,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 {
@ -157,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
@ -324,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

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

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

@ -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) {
@ -355,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

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

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

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