Merge branch 'main' into passkey-registration

This commit is contained in:
Max Peintner
2023-06-26 14:47:40 +02:00
30 changed files with 1309 additions and 116 deletions

View File

@@ -1,26 +1,76 @@
name: Test
on:
pull_request:
name: Quality
on: pull_request
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
test:
name: Test
quality:
name: Ensure Quality
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: 'read'
strategy:
fail-fast: false
matrix:
command:
- lint
- test:unit
- test:integration
steps:
- name: Checkout Repo
uses: actions/checkout@v2
- name: Setup pnpm 7
uses: pnpm/action-setup@v2
with:
version: 7
- name: Setup Node.js 16.x
uses: actions/setup-node@v2
with:
node-version: 16.x
- uses: pnpm/action-setup@v2
name: Install pnpm
id: pnpm-install
with:
version: 7
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- uses: actions/cache@v3
name: Setup Cypress binary cache
with:
path: ~/.cache/Cypress
key: ${{ runner.os }}-cypress-binary-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-cypress-binary-
if: ${{ matrix.command }} == "test:integration"
- name: Install Dependencies
id: deps
run: pnpm install
- name: Test
id: test
run: pnpm test
- name: Check
id: check
run: pnpm ${{ matrix.command }}

4
.gitignore vendored
View File

@@ -7,8 +7,8 @@ dist
dist-ssr
*.local
.env
apps/login/.env.local.?.bak
apps/login/.env.local.??.bak
apps/login/.env.local
apps/login/.env.acceptance
.cache
server/dist
public/dist

View File

@@ -38,25 +38,6 @@ However, it might be easier to develop against your ZITADEL Cloud instance
if you don't have docker installed
or have limited resources on your local machine.
### Testing
You can execute the following commands in the following directories:
- apps/login
- packages/zitadel-client
- packages/zitadel-server
- packages/zitadel-react
- packages/zitadel-next
- The projects root directory: all tests in the project are executed
```sh
# Run all once
pnpm test
# Rerun tests on file changes
pnpm test:watch
```
### Developing Against Your Local ZITADEL Instance
```sh
@@ -68,15 +49,18 @@ docker compose --file ./acceptance/docker-compose.yaml pull
# Run ZITADEL and configure ./apps/login/.env.local
docker compose --file ./acceptance/docker-compose.yaml run setup
# Configure your shell to use the environment variables written to ./apps/login/.env.acceptance
source ./apps/login/.env.acceptance
```
### Developing Against Your ZITADEL Cloud Instance
Create the file ./apps/login/.env.local with the following content:
Configure your shell by exporting the following environment variables:
```sh
ZITADEL_API_URL=<your cloud instance URL here>
ZITADEL_ORG_ID=<your service accounts organization id here>
ZITADEL_SERVICE_USER_TOKEN=<your service account personal access token here>
export ZITADEL_API_URL=<your cloud instance URL here>
export ZITADEL_ORG_ID=<your service accounts organization id here>
export ZITADEL_SERVICE_USER_TOKEN=<your service account personal access token here>
```
### Setting up local environment
@@ -94,4 +78,20 @@ pnpm dev
The application is now available at `http://localhost:3000`
### Testing
You can execute the following commands `pnpm test` for a single test run or `pnpm test:watch` in the following directories:
- apps/login
- packages/zitadel-client
- packages/zitadel-server
- packages/zitadel-react
- packages/zitadel-next
- The projects root directory: all tests in the project are executed
In apps/login, these commands also spin up the application and a ZITADEL gRPC API mock server to run integration tests using [Cypress](https://www.cypress.io/) against them.
If you want to run the integration tests standalone against an environment of your choice, navigate to ./apps/login, [configure your shell as you like](# Developing Against Your ZITADEL Cloud Instance) and run `pnpm test:integration:run` or `pnpm test:integration:open`.
Then you need to lifecycle the mock process using the command `pnpm mock` or the more fine grained commands `pnpm mock:build`, `pnpm mock:build:nocache`, `pnpm mock:run` and `pnpm mock:destroy`.
That's it! 🎉

View File

@@ -11,7 +11,7 @@ echo "Using audience ${AUDIENCE} for which the key is used."
SERVICE=${SERVICE:-$AUDIENCE}
echo "Using the service ${SERVICE} to connect to ZITADEL. For example in docker compose this can differ from the audience."
WRITE_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login/.env.local}
WRITE_ENVIRONMENT_FILE=${WRITE_ENVIRONMENT_FILE:-$(dirname "$0")/../apps/login/.env.acceptance}
echo "Writing environment file to ${WRITE_ENVIRONMENT_FILE} when done."
AUDIENCE_HOST="$(echo $AUDIENCE | cut -d/ -f3)"
@@ -44,27 +44,6 @@ echo "${ORG_RESPONSE}" | jq
ORG_ID=$(echo -n ${ORG_RESPONSE} | jq --raw-output '.org.id')
echo "Extracted default org id ${ORG_ID}"
ENVIRONMENT_BACKUP_FILE=${WRITE_ENVIRONMENT_FILE}
# If the original file already exists, rename it
if [[ -e ${WRITE_ENVIRONMENT_FILE} ]]; then
if grep -q 'localhost' ${WRITE_ENVIRONMENT_FILE}; then
echo "Current environment file ${WRITE_ENVIRONMENT_FILE} contains localhost. Overwriting:"
cat ${WRITE_ENVIRONMENT_FILE}
else
i=0
# If a backup file already exists, increment counter until a free filename is found
while [[ -e ${ENVIRONMENT_BACKUP_FILE}.${i}.bak ]]; do
let "i++"
if [[ ${i} -eq 50 ]]; then
echo "Warning: Too many backup files (limit is 50), overwriting ${ENVIRONMENT_BACKUP_FILE}.${i}.bak"
break
fi
done
mv ${WRITE_ENVIRONMENT_FILE} ${ENVIRONMENT_BACKUP_FILE}.${i}.bak
echo "Renamed existing environment file to ${ENVIRONMENT_BACKUP_FILE}.${i}.bak"
fi
fi
echo "ZITADEL_API_URL=${AUDIENCE}
ZITADEL_ORG_ID=${ORG_ID}
ZITADEL_SERVICE_USER_TOKEN=${TOKEN}" > ${WRITE_ENVIRONMENT_FILE}

View File

@@ -0,0 +1 @@
ZITADEL_API_URL=http://localhost:22222

View File

@@ -1,16 +1,19 @@
import type { Config } from "@jest/types";
import { pathsToModuleNameMapper } from "ts-jest";
import { compilerOptions } from "./tsconfig.json";
import { compilerOptions } from "../tsconfig.json";
// We make these type imports explicit, so IDEs with their own typescript engine understand them, too.
import type {} from "@testing-library/jest-dom";
export default async (): Promise<Config.InitialOptions> => {
return {
preset: "ts-jest",
transform: {
"^.+\\.tsx?$": ["ts-jest", { tsconfig: "./__test__/tsconfig.json" }],
"^.+\\.tsx?$": ["ts-jest", { tsconfig: "<rootDir>/tsconfig.json" }],
},
setupFilesAfterEnv: ["@testing-library/jest-dom/extend-expect"],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
prefix: "<rootDir>/",
prefix: "<rootDir>/../",
}),
testEnvironment: "jsdom",
testRegex: "/__test__/.*\\.test\\.tsx?$",

2
apps/login/cypress/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
screenshots
videos

View File

@@ -0,0 +1,12 @@
import { defineConfig } from "cypress";
export default defineConfig({
reporter: "list",
e2e: {
baseUrl: "http://localhost:3000",
specPattern: "cypress/integration/**/*.cy.{js,jsx,ts,tsx}",
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
});

View File

@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -0,0 +1,21 @@
import { addStub, removeStub } from "../support/mock";
describe("/verify", () => {
it("redirects after successful email verification", () => {
removeStub("zitadel.user.v2alpha.UserService", "VerifyEmail");
addStub("zitadel.user.v2alpha.UserService", "VerifyEmail");
cy.visit("/verify?userID=123&code=abc&submit=true");
cy.location("pathname", { timeout: 10_000 }).should("eq", "/username");
});
it("shows an error if validation failed", () => {
removeStub("zitadel.user.v2alpha.UserService", "VerifyEmail");
addStub("zitadel.user.v2alpha.UserService", "VerifyEmail", {
code: 3,
error: "error validating code",
});
// TODO: Avoid uncaught exception in application
cy.once("uncaught:exception", () => false);
cy.visit("/verify?userID=123&code=abc&submit=true");
cy.contains("error validating code");
});
});

View File

@@ -0,0 +1,37 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }

View File

@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import "./commands";
// Alternatively you can use CommonJS syntax:
// require('./commands')

View File

@@ -0,0 +1,26 @@
export function removeStub(service: string, method: string) {
return cy.request({
url: "http://localhost:22220/v1/stubs",
method: "DELETE",
qs: {
service: service,
method: method,
},
});
}
export function addStub(service: string, method: string, out?: any) {
return cy.request({
url: "http://localhost:22220/v1/stubs",
method: "POST",
body: {
stubs: [
{
service: service,
method: method,
out: out,
},
],
},
});
}

View File

@@ -0,0 +1,9 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress", "node"]
},
"include": ["**/*.ts"]
}

View File

@@ -0,0 +1,20 @@
FROM bufbuild/buf:1.21.0 as protos
RUN buf export https://github.com/envoyproxy/protoc-gen-validate.git --path validate --output /proto
RUN buf export https://github.com/grpc-ecosystem/grpc-gateway.git --path protoc-gen-openapiv2 --output /proto
RUN buf export https://github.com/googleapis/googleapis.git --path google/api/annotations.proto --path google/api/http.proto --path google/api/field_behavior.proto --output /proto
RUN buf export https://github.com/zitadel/zitadel.git --path ./proto/zitadel --output /proto
FROM scratch AS config
COPY mocked-services.cfg .
COPY initial-stubs initial-stubs
COPY --from=protos /proto .
FROM golang:1.20.5-alpine3.18 as grpc-mock
RUN go install github.com/eliobischof/grpc-mock/cmd/grpc-mock@01b09f60db1b501178af59bed03b2c22661df48c
COPY --from=config / .
ENTRYPOINT [ "sh", "-c", "grpc-mock -v 1 -proto $(tr '\n' ',' < ./mocked-services.cfg) -stub-dir ./initial-stubs" ]

View File

@@ -0,0 +1,17 @@
[
{
"service": "zitadel.settings.v2alpha.SettingsService",
"method": "GetBrandingSettings",
"out": {}
},
{
"service": "zitadel.settings.v2alpha.SettingsService",
"method": "GetLegalAndSupportSettings",
"out": {}
},
{
"service": "zitadel.settings.v2alpha.SettingsService",
"method": "GetPasswordComplexitySettings",
"out": {}
}
]

View File

@@ -0,0 +1,6 @@
zitadel/user/v2alpha/user_service.proto
zitadel/session/v2alpha/session_service.proto
zitadel/settings/v2alpha/settings_service.proto
zitadel/management.proto
zitadel/auth.proto
zitadel/admin.proto

View File

@@ -9,7 +9,7 @@ const nextConfig = {
remotePatterns: [
{
protocol: "https",
hostname: process.env.ZITADEL_API_URL.replace("https://", ""),
hostname: process.env.ZITADEL_API_URL?.replace("https://", "") || "",
port: "",
pathname: "/**",
},

View File

@@ -3,15 +3,27 @@
"private": true,
"scripts": {
"dev": "next dev",
"test": "jest",
"test:watch": "jest --watch",
"test": "concurrently --timings --kill-others-on-fail 'npm:test:unit' 'npm:test:integration'",
"test:watch": "concurrently --kill-others 'npm:test:unit:watch' 'npm:test:integration:watch'",
"test:unit": "jest --config ./__test__/jest.config.ts",
"test:unit:watch": "pnpm test:unit --watch",
"test:integration": "pnpm mock:build && concurrently --names 'mock,test' --success command-test --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test start http://localhost:3000 \"test:integration:run\"'",
"test:integration:watch": "concurrently --names 'mock,test' --kill-others 'pnpm:mock' 'env-cmd -f ./.env.integration start-server-and-test dev http://localhost:3000 \"pnpm nodemon -e js,jsx,ts,tsx,css,scss --ignore \\\"__test__/**\\\" --exec \\\"pnpm test:integration:run\\\"\"'",
"test:integration:run": "cypress run --config-file ./cypress/cypress.config.ts --quiet",
"test:integration:open": "cypress open --config-file ./cypress/cypress.config.ts",
"mock": "pnpm mock:build && pnpm mock:run",
"mock:run": "pnpm mock:stop && docker run --rm --name zitadel-mock-grpc-server --publish 22220:22220 --publish 22222:22222 zitadel-mock-grpc-server",
"mock:build": "DOCKER_BUILDKIT=1 docker build --tag zitadel-mock-grpc-server ./mock",
"mock:build:nocache": "pnpm mock:build --no-cache",
"mock:stop": "docker rm --force zitadel-mock-grpc-server 2>/dev/null || true",
"mock:destroy": "docker rmi --force zitadel-mock-grpc-server 2>/dev/null || true",
"lint": "next lint && prettier --check .",
"lint:fix": "prettier --write .",
"lint-staged": "lint-staged",
"build": "next build",
"prestart": "build",
"prestart": "pnpm build",
"start": "next start",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
"clean": "pnpm mock:destroy && rm -rf .turbo && rm -rf node_modules && rm -rf .next"
},
"git": {
"pre-commit": "lint-staged"
@@ -55,15 +67,21 @@
"@vercel/git-hooks": "1.0.0",
"@zitadel/tsconfig": "workspace:*",
"autoprefixer": "10.4.13",
"concurrently": "^8.1.0",
"cypress": "^12.14.0",
"del-cli": "5.0.0",
"env-cmd": "^10.1.0",
"eslint-config-zitadel": "workspace:*",
"grpc-tools": "1.11.3",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"jest-silent-reporter": "^0.5.0",
"lint-staged": "13.0.3",
"make-dir-cli": "3.0.0",
"nodemon": "^2.0.22",
"postcss": "8.4.21",
"prettier-plugin-tailwindcss": "0.1.13",
"start-server-and-test": "^2.0.0",
"tailwindcss": "3.2.4",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",

21
apps/login/turbo.json Normal file
View File

@@ -0,0 +1,21 @@
{
"extends": ["//"],
"pipeline": {
"build": {
"outputs": ["dist/**", ".next/**", "!.next/cache/**"],
"dependsOn": ["^build"]
},
"test": {
"dependsOn": ["@zitadel/server#build", "@zitadel/react#build"]
},
"test:integration": {
"dependsOn": ["@zitadel/server#build", "@zitadel/react#build"]
},
"test:unit": {
"dependsOn": ["@zitadel/server#build"]
},
"test:watch": {
"dependsOn": ["@zitadel/server#build", "@zitadel/react#build"]
}
}
}

View File

@@ -4,9 +4,12 @@
"generate": "turbo run generate",
"build": "turbo run build",
"test": "turbo run test",
"test:unit": "turbo run test:unit",
"test:integration": "turbo run test:integration",
"test:watch": "turbo run test:watch",
"dev": "turbo run dev --no-cache --continue",
"lint": "turbo run lint",
"lint:fix": "turbo run lint:fix",
"clean": "turbo run clean && rm -rf node_modules",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"changeset": "changeset",

View File

@@ -11,10 +11,11 @@
],
"scripts": {
"generate": "buf generate https://github.com/zitadel/zitadel.git --path ./proto/zitadel",
"prebuild": "pnpm run generate",
"build": "tsup src/index.ts --format esm,cjs --dts",
"test": "jest",
"test:watch": "jest --watch",
"test": "pnpm test:unit",
"test:watch": "pnpm test:unit:watch",
"test:unit": "jest",
"test:unit:watch": "jest --watch",
"dev": "tsup src/index.ts --format esm,cjs --watch --dts",
"lint": "eslint \"src/**/*.ts*\"",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"

View File

@@ -0,0 +1,21 @@
{
"extends": [
"//"
],
"pipeline": {
"generate": {
"outputs": [
"src/proto/**"
],
"cache": true
},
"build": {
"outputs": [
"dist/**"
],
"dependsOn": [
"generate"
]
}
}
}

View File

@@ -11,8 +11,10 @@
],
"scripts": {
"build": "tsup src/index.tsx --format esm,cjs --dts --external react",
"test": "jest",
"test:watch": "jest --watch",
"test": "pnpm test:unit",
"test:watch": "pnpm test:unit:watch",
"test:unit": "jest",
"test:unit:watch": "jest --watch",
"dev": "tsup src/index.tsx --format esm,cjs --watch --dts --external react",
"lint": "eslint \"src/**/*.ts*\"",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"

View File

@@ -13,8 +13,10 @@
},
"scripts": {
"build": "tsup",
"test": "jest",
"test:watch": "jest --watch",
"test": "pnpm test:unit",
"test:watch": "pnpm test:unit:watch",
"test:unit": "jest",
"test:unit:watch": "jest --watch",
"dev": "tsup --watch",
"lint": "eslint \"src/**/*.ts*\"",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",

View File

@@ -0,0 +1,12 @@
{
"extends": [
"//"
],
"pipeline": {
"build": {
"outputs": [
"dist/**"
]
}
}
}

View File

@@ -12,10 +12,11 @@
],
"scripts": {
"generate": "buf generate https://github.com/zitadel/zitadel.git --path ./proto/zitadel",
"prebuild": "pnpm run generate",
"build": "tsup --dts",
"test": "jest",
"test:watch": "jest --watch",
"test": "pnpm test:unit",
"test:watch": "pnpm test:unit:watch",
"test:unit": "jest",
"test:unit:watch": "jest --watch",
"dev": "tsup --dts --watch",
"lint": "eslint \"src/**/*.ts*\"",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"

View File

@@ -0,0 +1,21 @@
{
"extends": [
"//"
],
"pipeline": {
"generate": {
"outputs": [
"src/proto/**"
],
"cache": true
},
"build": {
"outputs": [
"dist/**"
],
"dependsOn": [
"generate"
]
}
}
}

936
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,36 +2,17 @@
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"generate": {
"outputs": [
"src/proto/**"
],
"cache": true
},
"build": {
"outputs": [
"dist/**",
".next/**",
"!.next/cache/**"
],
"dependsOn": [
"lint",
"generate",
"^build"
]
},
"test": {
"dependsOn": [
"generate",
"@zitadel/server#build"
]
},
"build": {},
"test": {},
"test:unit": {},
"test:integration": {},
"test:watch": {
"dependsOn": [
"generate",
"@zitadel/server#build"
]
"persistent": true
},
"lint": {},
"lint:fix": {},
"dev": {
"cache": false,
"persistent": true