mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-07 03:52:10 +00:00
# Which Problems Are Solved - The previous monorepo in monorepo structure for the login app and its related packages was fragmented, complicated and buggy. - The process for building and testing the login container was inconsistent between local development and CI. - Lack of clear documentation as well as easy and reliable ways for non-frontend developers to reproduce and fix failing PR checks locally. # How the Problems Are Solved - Consolidated the login app and its related npm packages by moving the main package to `apps/login/apps/login` and merging `apps/login/packages/integration` and `apps/login/packages/acceptance` into the main `apps/login` package. - Migrated from Docker Compose-based test setups to dev container-based setups, adding support for multiple dev container configurations: - `.devcontainer/base` - `.devcontainer/turbo-lint-unit` - `.devcontainer/turbo-lint-unit-debug` - `.devcontainer/login-integration` - `.devcontainer/login-integration-debug` - Added npm scripts to run the new dev container setups, enabling exact reproduction of GitHub PR checks locally, and updated the pipeline to use these containers. - Cleaned up Dockerfiles and docker-bake.hcl files to only build the production image for the login app. - Cleaned up compose files to focus on dev environments in dev containers. - Updated `CONTRIBUTING.md` with guidance on running and debugging PR checks locally using the new dev container approach. - Introduced separate Dockerfiles for the login app to distinguish between using published client packages and building clients from local protos. - Ensured the login container is always built in the pipeline for use in integration and acceptance tests. - Updated Makefile and GitHub Actions workflows to use `--frozen-lockfile` for installing pnpm packages, ensuring reproducible installs. - Disabled GitHub release creation by the changeset action. - Refactored the `/build` directory structure for clarity and maintainability. - Added a `clean` command to `docks/package.json`. - Experimentally added `knip` to the `zitadel-client` package for improved linting of dependencies and exports. # Additional Changes - Fixed Makefile commands for consistency and reliability. - Improved the structure and clarity of the `/build` directory to support seamless integration of the login build. - Enhanced documentation and developer experience for running and debugging CI checks locally. # Additional Context - See updated `CONTRIBUTING.md` for new local development and debugging instructions. - These changes are a prerequisite for further improvements to the CI pipeline and local development workflow. - Closes #10276
191 lines
5.0 KiB
TypeScript
191 lines
5.0 KiB
TypeScript
import { Authenticator } from "@otplib/core";
|
|
import { createDigest, createRandomBytes } from "@otplib/plugin-crypto";
|
|
import { keyDecoder, keyEncoder } from "@otplib/plugin-thirty-two"; // use your chosen base32 plugin
|
|
import axios from "axios";
|
|
import dotenv from "dotenv";
|
|
import { request } from "gaxios";
|
|
import path from "path";
|
|
import { OtpType, userProps } from "./user";
|
|
|
|
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
|
|
|
export async function addUser(props: userProps) {
|
|
const body = {
|
|
username: props.email,
|
|
organization: {
|
|
orgId: props.organization,
|
|
},
|
|
profile: {
|
|
givenName: props.firstName,
|
|
familyName: props.lastName,
|
|
},
|
|
email: {
|
|
email: props.email,
|
|
isVerified: true,
|
|
},
|
|
phone: {
|
|
phone: props.phone,
|
|
isVerified: true,
|
|
},
|
|
password: {
|
|
password: props.password,
|
|
changeRequired: props.passwordChangeRequired ?? false,
|
|
},
|
|
};
|
|
if (!props.isEmailVerified) {
|
|
delete body.email.isVerified;
|
|
}
|
|
if (!props.isPhoneVerified) {
|
|
delete body.phone.isVerified;
|
|
}
|
|
|
|
return await listCall(`${process.env.ZITADEL_API_URL}/v2/users/human`, body);
|
|
}
|
|
|
|
export async function removeUserByUsername(username: string) {
|
|
const resp = await getUserByUsername(username);
|
|
if (!resp || !resp.result || !resp.result[0]) {
|
|
return;
|
|
}
|
|
await removeUser(resp.result[0].userId);
|
|
}
|
|
|
|
export async function removeUser(id: string) {
|
|
await deleteCall(`${process.env.ZITADEL_API_URL}/v2/users/${id}`);
|
|
}
|
|
|
|
async function deleteCall(url: string) {
|
|
try {
|
|
const response = await axios.delete(url, {
|
|
headers: {
|
|
Authorization: `Bearer ${process.env.ZITADEL_ADMIN_TOKEN}`,
|
|
},
|
|
});
|
|
|
|
if (response.status >= 400 && response.status !== 404) {
|
|
const error = `HTTP Error: ${response.status} - ${response.statusText}`;
|
|
console.error(error);
|
|
throw new Error(error);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error making request:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function getUserByUsername(username: string): Promise<any> {
|
|
const listUsersBody = {
|
|
queries: [
|
|
{
|
|
userNameQuery: {
|
|
userName: username,
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
return await listCall(`${process.env.ZITADEL_API_URL}/v2/users`, listUsersBody);
|
|
}
|
|
|
|
async function listCall(url: string, data: any): Promise<any> {
|
|
try {
|
|
const response = await axios.post(url, data, {
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${process.env.ZITADEL_ADMIN_TOKEN}`,
|
|
},
|
|
});
|
|
|
|
if (response.status >= 400) {
|
|
const error = `HTTP Error: ${response.status} - ${response.statusText}`;
|
|
console.error(error);
|
|
throw new Error(error);
|
|
}
|
|
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error("Error making request:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function activateOTP(userId: string, type: OtpType) {
|
|
let url = "otp_";
|
|
switch (type) {
|
|
case OtpType.sms:
|
|
url = url + "sms";
|
|
break;
|
|
case OtpType.email:
|
|
url = url + "email";
|
|
break;
|
|
}
|
|
|
|
await pushCall(`${process.env.ZITADEL_API_URL}/v2/users/${userId}/${url}`, {});
|
|
}
|
|
|
|
async function pushCall(url: string, data: any) {
|
|
try {
|
|
const response = await axios.post(url, data, {
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${process.env.ZITADEL_ADMIN_TOKEN}`,
|
|
},
|
|
});
|
|
|
|
if (response.status >= 400) {
|
|
const error = `HTTP Error: ${response.status} - ${response.statusText}`;
|
|
console.error(error);
|
|
throw new Error(error);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error making request:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function addTOTP(userId: string): Promise<string> {
|
|
const response = await listCall(`${process.env.ZITADEL_API_URL}/v2/users/${userId}/totp`, {});
|
|
const code = totp(response.secret);
|
|
await pushCall(`${process.env.ZITADEL_API_URL}/v2/users/${userId}/totp/verify`, { code: code });
|
|
return response.secret;
|
|
}
|
|
|
|
export function totp(secret: string) {
|
|
const authenticator = new Authenticator({
|
|
createDigest,
|
|
createRandomBytes,
|
|
keyDecoder,
|
|
keyEncoder,
|
|
});
|
|
// google authenticator usage
|
|
const token = authenticator.generate(secret);
|
|
|
|
// check if token can be used
|
|
if (!authenticator.verify({ token: token, secret: secret })) {
|
|
const error = `Generated token could not be verified`;
|
|
console.error(error);
|
|
throw new Error(error);
|
|
}
|
|
|
|
return token;
|
|
}
|
|
|
|
export async function eventualNewUser(id: string) {
|
|
return request({
|
|
url: `${process.env.ZITADEL_API_URL}/v2/users/${id}`,
|
|
method: "GET",
|
|
headers: {
|
|
Authorization: `Bearer ${process.env.ZITADEL_ADMIN_TOKEN}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
retryConfig: {
|
|
statusCodesToRetry: [[404, 404]],
|
|
retry: Number.MAX_SAFE_INTEGER, // totalTimeout limits the number of retries
|
|
totalTimeout: 10000, // 10 seconds
|
|
onRetryAttempt: (error) => {
|
|
console.warn(`Retrying to query new user ${id}: ${error.message}`);
|
|
},
|
|
},
|
|
});
|
|
}
|