test(session): load tests for session api (#9212)

# Which Problems Are Solved

We currently are not able to benchmark the performance of the session
api

# How the Problems Are Solved

Load tests were added to
- use sessions in oidc tokens analog
https://zitadel.com/docs/guides/integrate/login-ui/oidc-standard

# Additional Context

- Closes https://github.com/zitadel/zitadel/issues/7847
This commit is contained in:
Silvan 2025-01-29 13:08:20 +01:00 committed by GitHub
parent 679ab58fa1
commit b10428fb56
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 409 additions and 112 deletions

View File

@ -481,7 +481,7 @@ func (q *Queries) SearchApps(ctx context.Context, queries *AppSearchQueries, wit
return err
}, stmt, args...)
if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-aJnZL", "Errors.Internal")
return nil, zerrors.ThrowInternal(err, "QUERY-h9TeF", "Errors.Internal")
}
apps.State, err = q.latestState(ctx, appsTable)
return apps, err

View File

@ -207,7 +207,7 @@ func (q *Queries) SearchSMSConfigs(ctx context.Context, queries *SMSConfigsSearc
return err
}, stmt, args...)
if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-aJnZL", "Errors.Internal")
return nil, zerrors.ThrowInternal(err, "QUERY-l4bxm", "Errors.Internal")
}
configs.State, err = q.latestState(ctx, smsConfigsTable)
return configs, err

View File

@ -33,9 +33,21 @@ introspect: ensure_modules bundle
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} --out csv=output/introspect_${DATE}.csv
.PHONY: oidc_session
oidc_session: ensure_key_pair ensure_modules bundle
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/oidc_session.js --vus ${VUS} --duration ${DURATION} --out csv=output/oidc_session_${DATE}.csv
.PHONY: otp_session
otp_session: ensure_key_pair ensure_modules bundle
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/otp_session.js --vus ${VUS} --duration ${DURATION} --out csv=output/otp_session_${DATE}.csv
.PHONY: password_session
password_session: ensure_key_pair ensure_modules bundle
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/password_session.js --vus ${VUS} --duration ${DURATION} --out csv=output/otp_session_${DATE}.csv
.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} --out csv=output/add_session_${DATE}.csv
add_session: ensure_modules bundle
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/add_session.js --vus ${VUS} --duration ${DURATION} --out csv=output/add_session_${DATE}.csv
.PHONY: machine_jwt_profile_grant
machine_jwt_profile_grant: ensure_modules ensure_key_pair bundle

View File

@ -20,6 +20,8 @@ The use cases under tests are defined in `src/use_cases`. The implementation of
- `VUS`: Amount of parallel processes execute the test (default is 20)
- `DURATION`: Defines how long the tests are executed (default is `200s`)
- `ZITADEL_HOST`: URL of ZITADEL (default is `http://localhost:8080`)
- `ADMIN_LOGIN_NAME`: Loginanme of a human user with `IAM_OWNER`-role
- `ADMIN_PASSWORD`: password of the human user
To setup the tests we use the credentials of console and log in using an admin. The user must be able to create organizations and all resources inside organizations.
@ -50,6 +52,15 @@ Before you run the tests you need an initialized user. The tests don't implement
* `make add_session`
setup: creates human users
test: creates new sessions with user id check
* `make oidc_session`
setup: creates a machine user to create the auth request and session.
test: creates an auth request, a session and links the session to the auth request. Implementation of [this flow](https://zitadel.com/docs/guides/integrate/login-ui/oidc-standard).
* `make otp_session`
setup: creates 1 human user for each VU and adds email OTP to it
test: creates a session based on the login name of the user, sets the email OTP challenge to the session and afterwards checks the OTP code
* `make password_session`
setup: creates 1 human user for each VU and adds email OTP to it
test: creates a session based on the login name of the user and checks for the password on a second step
* `make machine_jwt_profile_grant`
setup: generates private/public key, creates machine users, adds a key
test: creates a token and calls user info

View File

@ -27,7 +27,7 @@
},
"scripts": {
"bundle": "webpack",
"lint": "prettier --check src",
"lint": "prettier --check src/**",
"lint:fix": "prettier --write src"
}
}

View File

@ -21,10 +21,27 @@ export function loginByUsernamePassword(user: User) {
}
const initLoginTrend = new Trend('login_ui_init_login_duration', true);
function initLogin(): Response {
const response = http.get(url('/oauth/v2/authorize', { searchParams: Client() }));
export function initLogin(clientId?: string): Response {
let params = {};
let expectedStatus = 200;
if (clientId) {
params = {
headers: {
'x-zitadel-login-client': clientId,
},
redirects: 0,
};
expectedStatus = 302;
}
const response = http.get(
url('/oauth/v2/authorize', {
searchParams: Client(),
}),
params,
);
check(response, {
'authorize status ok': (r) => r.status == 200 || fail(`init login failed: ${r}`),
'authorize status ok': (r) => r.status == expectedStatus || fail(`init login failed: ${JSON.stringify(r)}`),
});
initLoginTrend.add(response.timings.duration);
return response;

View File

@ -0,0 +1,25 @@
import http from 'k6/http';
import { Trend } from 'k6/metrics';
import url from './url';
import { check, fail } from 'k6';
const addIAMMemberTrend = new Trend('membership_iam_member', true);
export async function addIAMMember(userId: string, roles: string[], accessToken: string): Promise<void> {
const res = await http.post(
url('/admin/v1/members'),
JSON.stringify({
userId: userId,
roles: roles,
}),
{
headers: {
authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
},
);
check(res, {
'member added successful': (r) => r.status == 200 || fail(`unable add member: ${JSON.stringify(res)}`),
});
addIAMMemberTrend.add(res.timings.duration);
}

View File

@ -1,9 +1,9 @@
import { JSONObject, check, fail } from 'k6';
import encoding from 'k6/encoding';
import http, { RequestBody } from 'k6/http';
import http, { RequestBody, Response } from 'k6/http';
import { Trend } from 'k6/metrics';
import url from './url';
import { Config } from './config';
import { Client, Config } from './config';
// @ts-ignore Import module
import zitadel from 'k6/x/zitadel';
@ -79,9 +79,11 @@ export function introspect(jwt: string, token: string) {
const clientCredentialsTrend = new Trend('oidc_client_credentials_duration', true);
export function clientCredentials(clientId: string, clientSecret: string): Promise<Tokens> {
return new Promise((resolve, reject) => {
const response = http.asyncRequest('POST', configuration().token_endpoint,
const response = http.asyncRequest(
'POST',
configuration().token_endpoint,
{
grant_type: "client_credentials",
grant_type: 'client_credentials',
scope: 'openid profile urn:zitadel:iam:org:project:id:zitadel:aud',
client_id: clientId,
client_secret: clientSecret,
@ -91,26 +93,26 @@ export function clientCredentials(clientId: string, clientSecret: string): Promi
'Content-Type': 'application/x-www-form-urlencoded',
},
},
);
);
response.then((res) => {
check(res, {
'client credentials status ok': (r) => r.status === 200,
}) || reject(`client credentials request failed (client id: ${clientId}) status: ${res.status} body: ${res.body}`);
clientCredentialsTrend.add(res.timings.duration);
const tokens = new Tokens(res.json() as JSONObject)
const tokens = new Tokens(res.json() as JSONObject);
check(tokens, {
'client credentials token ok': (t) => t.accessToken !== undefined,
}) || reject(`client credentials access token missing (client id: ${clientId}`);
resolve(tokens)
resolve(tokens);
});
});
}
export interface TokenRequest {
payload(): RequestBody;
headers(): { [name: string]: string; };
headers(): { [name: string]: string };
}
const privateKey = open('../.keys/key.pem');
@ -126,46 +128,83 @@ export class JWTProfileRequest implements TokenRequest {
this.keyPayload = {
userId: userId,
// 1 minute
expiration: 60*1_000_000_000,
expiration: 60 * 1_000_000_000,
keyId: keyId,
};
}
payload(): RequestBody{
const assertion = zitadel.signJWTProfileAssertion(
this.keyPayload.userId,
this.keyPayload.keyId,
{
audience: [Config.host],
expiration: this.keyPayload.expiration,
key: privateKey
});
payload(): RequestBody {
const assertion = zitadel.signJWTProfileAssertion(this.keyPayload.userId, this.keyPayload.keyId, {
audience: [Config.host],
expiration: this.keyPayload.expiration,
key: privateKey,
});
return {
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
scope: 'openid',
assertion: `${assertion}`
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
scope: 'openid urn:zitadel:iam:org:project:id:zitadel:aud',
assertion: `${assertion}`,
};
};
public headers(): { [name: string]: string; } {
}
public headers(): { [name: string]: string } {
return {
'Content-Type': 'application/x-www-form-urlencoded'
'Content-Type': 'application/x-www-form-urlencoded',
};
};
}
}
const tokenDurationTrend = new Trend('oidc_token_duration', true);
export async function token(request: TokenRequest): Promise<Tokens> {
return http.asyncRequest('POST', configuration().token_endpoint,
request.payload(),
{
return http
.asyncRequest('POST', configuration().token_endpoint, request.payload(), {
headers: request.headers(),
},
).then((res) => {
tokenDurationTrend.add(res.timings.duration);
check(res, {
'token status ok': (r) => r.status === 200,
'access token returned': (r) => r.json('access_token')! != undefined && r.json('access_token')! != '',
})
.then((res) => {
tokenDurationTrend.add(res.timings.duration);
check(res, {
'token status ok': (r) => r.status === 200,
'access token returned': (r) => r.json('access_token')! != undefined && r.json('access_token')! != '',
});
return new Tokens(res.json() as JSONObject);
});
return new Tokens(res.json() as JSONObject);
}
const authRequestBiIDTrend = new Trend('oidc_auth_request_by_id_duration', true);
export async function authRequestByID(id: string, tokens: any): Promise<Response> {
const response = http.get(url(`/v2/oidc/auth_requests/${id}`), {
headers: {
Authorization: `Bearer ${tokens.accessToken}`,
},
});
};
check(response, {
'authorize status ok': (r) => r.status == 200 || fail(`auth request by failed: ${JSON.stringify(r)}`),
});
authRequestBiIDTrend.add(response.timings.duration);
return response;
}
const finalizeAuthRequestTrend = new Trend('oidc_auth_requst_by_id_duration', true);
export async function finalizeAuthRequest(id: string, session: any, tokens: any): Promise<Response> {
const res = await http.post(
url(`/v2/oidc/auth_requests/${id}`),
JSON.stringify({
session: {
sessionId: session.sessionId,
sessionToken: session.sessionToken,
},
}),
{
headers: {
Authorization: `Bearer ${tokens.accessToken}`,
'Content-Type': 'application/json',
// 'Accept': 'application/json',
'x-zitadel-login-client': tokens.info.client_id,
},
},
);
check(res, {
'finalize auth request status ok': (r) => r.status == 200 || fail(`finalize auth request failed: ${JSON.stringify(r)}`),
});
finalizeAuthRequestTrend.add(res.timings.duration);
return res;
}

View File

@ -3,33 +3,23 @@ import { Org } from './org';
import http from 'k6/http';
import url from './url';
import { check } from 'k6';
import { User } from './user';
export type Session = {
challenges: any;
id: string;
token: string;
};
const addSessionTrend = new Trend('session_add_session_duration', true);
export function createSession(user: User, org: Org, accessToken: string): Promise<Session> {
export function createSession(org: Org, accessToken: string, checks?: any): Promise<Session> {
return new Promise((resolve, reject) => {
let response = http.asyncRequest(
'POST',
url('/v2beta/sessions'),
JSON.stringify({
checks: {
user: {
userId: user.userId,
}
}
}),
{
headers: {
authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'x-zitadel-orgid': org.organizationId,
},
let response = http.asyncRequest('POST', url('/v2/sessions'), checks ? JSON.stringify({ checks: checks }) : null, {
headers: {
authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'x-zitadel-orgid': org.organizationId,
},
);
});
response.then((res) => {
check(res, {
'add Session status ok': (r) => r.status === 201,
@ -40,3 +30,29 @@ export function createSession(user: User, org: Org, accessToken: string): Promis
});
});
}
const setSessionTrend = new Trend('session_set_session_duration', true);
export function setSession(id: string, session: any, accessToken: string, challenges?: any, checks?: any): Promise<Session> {
const body = {
sessionToken: session.sessionToken,
checks: checks ? checks : null,
challenges: challenges ? challenges : null,
};
return new Promise((resolve, reject) => {
let response = http.asyncRequest('PATCH', url(`/v2/sessions/${id}`), JSON.stringify(body), {
headers: {
authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
// 'x-zitadel-orgid': org.organizationId,
},
});
response.then((res) => {
check(res, {
'set Session status ok': (r) => r.status === 200,
}) || reject(`unable to set Session status: ${res.status} body: ${res.body}`);
setSessionTrend.add(res.timings.duration);
resolve(res.json() as Session);
});
});
}

View File

@ -1,7 +1,7 @@
import { loginByUsernamePassword } from '../login_ui';
import { createOrg, removeOrg } from '../org';
import {createMachine, User, addMachineSecret} from '../user';
import {clientCredentials, userinfo} from '../oidc';
import { createMachine, User, addMachineSecret } from '../user';
import { clientCredentials, userinfo } from '../oidc';
import { Config, MaxVUs } from '../config';
export async function setup() {
@ -37,10 +37,9 @@ export async function setup() {
}
export default function (data: any) {
clientCredentials(data.machines[__VU - 1].loginName, data.machines[__VU - 1].password)
.then((token) => {
userinfo(token.accessToken!)
})
clientCredentials(data.machines[__VU - 1].loginName, data.machines[__VU - 1].password).then((token) => {
userinfo(token.accessToken!);
});
}
export function teardown(data: any) {

View File

@ -1,7 +1,7 @@
import { loginByUsernamePassword } from '../login_ui';
import { createOrg, removeOrg } from '../org';
import {createMachine, User, addMachineKey} from '../user';
import {JWTProfileRequest, token, userinfo} from '../oidc';
import { createMachine, User, addMachineKey } from '../user';
import { JWTProfileRequest, token, userinfo } from '../oidc';
import { Config, MaxVUs } from '../config';
import encoding from 'k6/encoding';
@ -10,10 +10,10 @@ const publicKey = encoding.b64encode(open('../.keys/key.pem.pub'));
export async function setup() {
const tokens = loginByUsernamePassword(Config.admin as User);
console.info('setup: admin signed in');
const org = await createOrg(tokens.accessToken!);
console.info(`setup: org (${org.organizationId}) created`);
let machines = (
await Promise.all(
Array.from({ length: MaxVUs() }, (_, i) => {
@ -24,16 +24,11 @@ export async function setup() {
return { userId: machine.userId, loginName: machine.loginNames[0] };
});
console.info(`setup: ${machines.length} machines created`);
let keys = (
await Promise.all(
machines.map((machine) => {
return addMachineKey(
machine.userId,
org,
tokens.accessToken!,
publicKey,
);
return addMachineKey(machine.userId, org, tokens.accessToken!, publicKey);
}),
)
).map((key, i) => {
@ -45,7 +40,7 @@ export async function setup() {
}
export default function (data: any) {
token(new JWTProfileRequest(data.machines[__VU - 1].userId, data.machines[__VU - 1].keyId))
token(new JWTProfileRequest(data.machines[__VU - 1].userId, data.machines[__VU - 1].keyId));
}
export function teardown(data: any) {

View File

@ -1,7 +1,7 @@
import { loginByUsernamePassword } from '../login_ui';
import { createOrg, removeOrg } from '../org';
import {createMachine, User, addMachineKey} from '../user';
import {JWTProfileRequest, token, userinfo} from '../oidc';
import { createMachine, User, addMachineKey } from '../user';
import { JWTProfileRequest, token, userinfo } from '../oidc';
import { Config } from '../config';
import encoding from 'k6/encoding';
@ -10,20 +10,20 @@ const publicKey = encoding.b64encode(open('../.keys/key.pem.pub'));
export async function setup() {
const tokens = loginByUsernamePassword(Config.admin as User);
console.info('setup: admin signed in');
const org = await createOrg(tokens.accessToken!);
console.info(`setup: org (${org.organizationId}) created`);
const machine = await createMachine(`zitachine`, org, tokens.accessToken!);
console.info(`setup: machine ${machine.userId} created`);
const key = await addMachineKey(machine.userId, org, tokens.accessToken!, publicKey);
console.info(`setup: key ${key.keyId} added`);
return { tokens, machine: {userId: machine.userId, keyId: key.keyId}, org };
return { tokens, machine: { userId: machine.userId, keyId: key.keyId }, org };
}
export default function (data: any) {
token(new JWTProfileRequest(data.machine.userId, data.machine.keyId))
token(new JWTProfileRequest(data.machine.userId, data.machine.keyId));
}
export function teardown(data: any) {

View File

@ -1,9 +1,9 @@
import { loginByUsernamePassword } from '../login_ui';
import { createOrg, removeOrg } from '../org';
import { User, createHuman } from '../user';
import { loginByUsernamePassword } from '../../login_ui';
import { createOrg, removeOrg } from '../../org';
import { User, createHuman } from '../../user';
import { Trend } from 'k6/metrics';
import { Config, MaxVUs } from '../config';
import { createSession } from '../session';
import { Config, MaxVUs } from '../../config';
import { createSession } from '../../session';
import { check } from 'k6';
export async function setup() {
@ -27,12 +27,16 @@ export async function setup() {
const addSessionTrend = new Trend('add_session_duration', true);
export default async function (data: any) {
const start = new Date();
const session = await createSession(data.users[__VU - 1], data.org, data.tokens.accessToken);
const session = await createSession(data.org, data.tokens.accessToken, {
user: {
userId: data.users[__VU - 1].userId,
},
});
check(session, {
'add session is status ok': (s) => s.id !== "",
'add session is status ok': (s) => s.id !== '',
});
addSessionTrend.add(new Date().getTime() - start.getTime());
}

View File

@ -0,0 +1,53 @@
import { loginByUsernamePassword, initLogin } from '../../login_ui';
import { createOrg, removeOrg } from '../../org';
import { User, addMachineKey, createMachine } from '../../user';
import { Trend } from 'k6/metrics';
import { Config } from '../../config';
import { check } from 'k6';
import { finalizeAuthRequest, JWTProfileRequest, token } from '../../oidc';
import { createSession } from '../../session';
import encoding from 'k6/encoding';
import { addIAMMember } from '../../membership';
const publicKey = encoding.b64encode(open('../.keys/key.pem.pub'));
export async function setup() {
const adminTokens = loginByUsernamePassword(Config.admin as User);
console.log('setup: admin signed in');
const org = await createOrg(adminTokens.accessToken!);
console.log(`setup: org (${org.organizationId}) created`);
const loginUser = await createMachine('load-test', org, adminTokens.accessToken!);
const loginUserKey = await addMachineKey(loginUser.userId, org, adminTokens.accessToken!, publicKey);
await addIAMMember(loginUser.userId, ['IAM_LOGIN_CLIENT'], adminTokens.accessToken!);
const tokens = await token(new JWTProfileRequest(loginUser.userId, loginUserKey.keyId));
return { tokens, user: loginUser, key: loginUserKey, org, adminTokens };
}
// implements the flow described in
// https://zitadel.com/docs/guides/integrate/login-ui/oidc-standard
const addSessionTrend = new Trend('oidc_session_duration', true);
export default async function (data: any) {
const start = new Date();
const authorizeResponse = initLogin(data.tokens.info.client_id);
const authRequestId = new URLSearchParams(authorizeResponse.headers['Location']).values().next().value;
check(authRequestId, {
'auth request id returned': (s) => s !== '',
});
const session = await createSession(data.org, data.tokens.accessToken, {
user: {
userId: data.user.userId,
},
});
await finalizeAuthRequest(authRequestId!, session, data.tokens!);
addSessionTrend.add(new Date().getTime() - start.getTime());
}
export function teardown(data: any) {
removeOrg(data.org, data.adminTokens.accessToken);
}

View File

@ -0,0 +1,57 @@
import { loginByUsernamePassword } from '../../login_ui';
import { createOrg, removeOrg } from '../../org';
import { createHuman, setEmailOTPOnHuman, User } from '../../user';
import { Trend } from 'k6/metrics';
import { Config, MaxVUs } from '../../config';
import { createSession, setSession } from '../../session';
export async function setup() {
const tokens = loginByUsernamePassword(Config.admin as User);
console.log('setup: admin signed in');
const org = await createOrg(tokens.accessToken!);
console.log(`setup: org (${org.organizationId}) created`);
const humanPromises = Array.from({ length: MaxVUs() }, (_, i) => {
return createHuman(`zitizen-${i}`, org, tokens.accessToken!);
});
const humans = (await Promise.all(humanPromises)).map((user) => {
setEmailOTPOnHuman(user, org, tokens.accessToken!);
return { userId: user.userId, loginName: user.loginNames[0], password: 'Password1!' };
});
console.log(`setup: ${humans.length} users created`);
return { tokens, org, users: humans };
}
// implements the flow described in
// https://zitadel.com/docs/guides/integrate/login-ui/oidc-standard
const otpSessionTrend = new Trend('otp_session_duration', true);
export default async function (data: any) {
const start = new Date();
let session = await createSession(data.org, data.tokens.accessToken, {
user: {
loginName: data.users[__VU - 1].loginName,
},
});
const sessionId = (session as any).sessionId;
session = await setSession(sessionId, session, data.tokens.accessToken, {
otpEmail: {
return_code: {},
},
});
session = await setSession(sessionId, session, data.tokens.accessToken, null, {
otpEmail: {
code: session.challenges.otpEmail,
},
});
otpSessionTrend.add(new Date().getTime() - start.getTime());
}
export function teardown(data: any) {
removeOrg(data.org, data.tokens.accessToken);
}

View File

@ -0,0 +1,49 @@
import { loginByUsernamePassword } from '../../login_ui';
import { createOrg, removeOrg } from '../../org';
import { createHuman, setEmailOTPOnHuman, User } from '../../user';
import { Trend } from 'k6/metrics';
import { Config, MaxVUs } from '../../config';
import { createSession, setSession } from '../../session';
export async function setup() {
const tokens = loginByUsernamePassword(Config.admin as User);
console.log('setup: admin signed in');
const org = await createOrg(tokens.accessToken!);
console.log(`setup: org (${org.organizationId}) created`);
const humanPromises = Array.from({ length: MaxVUs() }, (_, i) => {
return createHuman(`zitizen-${i}`, org, tokens.accessToken!);
});
const humans = (await Promise.all(humanPromises)).map((user) => {
setEmailOTPOnHuman(user, org, tokens.accessToken!);
return { userId: user.userId, loginName: user.loginNames[0], password: 'Password1!' };
});
console.log(`setup: ${humans.length} users created`);
return { tokens, org, users: humans };
}
const passwordSessionTrend = new Trend('password_session_duration', true);
export default async function (data: any) {
const start = new Date();
let session = await createSession(data.org, data.tokens.accessToken, {
user: {
loginName: data.users[__VU - 1].loginName,
},
});
const sessionId = (session as any).sessionId;
session = await setSession(sessionId, session, data.tokens.accessToken, null, {
password: {
password: data.users[__VU - 1].password,
},
});
passwordSessionTrend.add(new Date().getTime() - start.getTime());
}
export function teardown(data: any) {
removeOrg(data.org, data.tokens.accessToken);
}

View File

@ -19,7 +19,7 @@ export function createHuman(username: string, org: Org, accessToken: string): Pr
return new Promise((resolve, reject) => {
let response = http.asyncRequest(
'POST',
url('/v2beta/users/human'),
url('/v2/users/human'),
JSON.stringify({
username: username,
organization: {
@ -69,6 +69,23 @@ export function createHuman(username: string, org: Org, accessToken: string): Pr
});
}
const setEmailOTPOnHumanTrend = new Trend('set_human_email_otp_duration', true);
export async function setEmailOTPOnHuman(user: User, org: Org, accessToken: string): Promise<void> {
const response = await http.asyncRequest('POST', url(`/v2/users/${user.userId}/otp_email`), null, {
headers: {
authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'x-zitadel-orgid': org.organizationId,
},
});
check(response, {
'set email otp status ok': (r) => r.status === 200,
});
setEmailOTPOnHumanTrend.add(response.timings.duration);
return;
}
const updateHumanTrend = new Trend('update_human_duration', true);
export function updateHuman(
payload: any = {},
@ -172,8 +189,8 @@ export function addMachinePat(userId: string, org: Org, accessToken: string): Pr
}
export type MachineSecret = {
clientId: string;
clientSecret: string;
clientId: string;
clientSecret: string;
};
const addMachineSecretTrend = new Trend('user_add_machine_secret_duration', true);
@ -204,20 +221,23 @@ export type MachineKey = {
const addMachineKeyTrend = new Trend('user_add_machine_key_duration', true);
export function addMachineKey(userId: string, org: Org, accessToken: string, publicKey?: string): Promise<MachineKey> {
return new Promise((resolve, reject) => {
let response = http.asyncRequest('POST', url(`/management/v1/users/${userId}/keys`),
JSON.stringify({
type: 'KEY_TYPE_JSON',
userId: userId,
// base64 encoded public key
publicKey: publicKey
}),
{
headers: {
authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'x-zitadel-orgid': org.organizationId,
let response = http.asyncRequest(
'POST',
url(`/management/v1/users/${userId}/keys`),
JSON.stringify({
type: 'KEY_TYPE_JSON',
userId: userId,
// base64 encoded public key
publicKey: publicKey,
}),
{
headers: {
authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'x-zitadel-orgid': org.organizationId,
},
},
});
);
response.then((res) => {
check(res, {
'generate machine key status ok': (r) => r.status === 200,

View File

@ -5,7 +5,7 @@ const GlobEntries = require('webpack-glob-entries');
module.exports = {
mode: 'production',
entry: GlobEntries('./src/use_cases/*.ts'), // Generates multiple entry for each test
entry: GlobEntries('./src/use_cases/**/*.ts'), // Generates multiple entry for each test
output: {
path: path.join(__dirname, 'dist'),
libraryTarget: 'commonjs',