mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-07 07:16:54 +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
187 lines
4.5 KiB
Go
187 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/zitadel/oidc/v3/example/server/exampleop"
|
|
"github.com/zitadel/oidc/v3/example/server/storage"
|
|
)
|
|
|
|
func main() {
|
|
apiURL := os.Getenv("API_URL")
|
|
pat := readPAT(os.Getenv("PAT_FILE"))
|
|
domain := os.Getenv("API_DOMAIN")
|
|
schema := os.Getenv("SCHEMA")
|
|
host := os.Getenv("HOST")
|
|
port := os.Getenv("PORT")
|
|
|
|
logger := slog.New(
|
|
slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
|
AddSource: true,
|
|
Level: slog.LevelDebug,
|
|
}),
|
|
)
|
|
|
|
issuer := fmt.Sprintf("%s://%s:%s/", schema, host, port)
|
|
redirectURI := fmt.Sprintf("%s/idps/callback", apiURL)
|
|
|
|
clientID := "web"
|
|
clientSecret := "secret"
|
|
storage.RegisterClients(
|
|
storage.WebClient(clientID, clientSecret, redirectURI),
|
|
)
|
|
|
|
storage := storage.NewStorage(storage.NewUserStore(issuer))
|
|
router := exampleop.SetupServer(issuer, storage, logger, false)
|
|
|
|
server := &http.Server{
|
|
Addr: ":" + port,
|
|
Handler: router,
|
|
}
|
|
go func() {
|
|
if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
|
log.Fatalf("HTTP server error: %v", err)
|
|
}
|
|
log.Println("Stopped serving new connections.")
|
|
}()
|
|
|
|
createZitadelResources(apiURL, pat, domain, issuer, clientID, clientSecret)
|
|
|
|
sigChan := make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
|
<-sigChan
|
|
|
|
shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer shutdownRelease()
|
|
|
|
if err := server.Shutdown(shutdownCtx); err != nil {
|
|
log.Fatalf("HTTP shutdown error: %v", err)
|
|
}
|
|
}
|
|
|
|
func readPAT(path string) string {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
pat, err := io.ReadAll(f)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return strings.Trim(string(pat), "\n")
|
|
}
|
|
|
|
func createZitadelResources(apiURL, pat, domain, issuer, clientID, clientSecret string) error {
|
|
idpID, err := CreateIDP(apiURL, pat, domain, issuer, clientID, clientSecret)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ActivateIDP(apiURL, pat, domain, idpID)
|
|
}
|
|
|
|
type createIDP struct {
|
|
Name string `json:"name"`
|
|
Issuer string `json:"issuer"`
|
|
ClientId string `json:"clientId"`
|
|
ClientSecret string `json:"clientSecret"`
|
|
Scopes []string `json:"scopes"`
|
|
ProviderOptions providerOptions `json:"providerOptions"`
|
|
IsIdTokenMapping bool `json:"isIdTokenMapping"`
|
|
UsePkce bool `json:"usePkce"`
|
|
}
|
|
|
|
type providerOptions struct {
|
|
IsLinkingAllowed bool `json:"isLinkingAllowed"`
|
|
IsCreationAllowed bool `json:"isCreationAllowed"`
|
|
IsAutoCreation bool `json:"isAutoCreation"`
|
|
IsAutoUpdate bool `json:"isAutoUpdate"`
|
|
AutoLinking string `json:"autoLinking"`
|
|
}
|
|
|
|
type idp struct {
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
func CreateIDP(apiURL, pat, domain string, issuer, clientID, clientSecret string) (string, error) {
|
|
createIDP := &createIDP{
|
|
Name: "OIDC",
|
|
Issuer: issuer,
|
|
ClientId: clientID,
|
|
ClientSecret: clientSecret,
|
|
Scopes: []string{"openid", "profile", "email"},
|
|
ProviderOptions: providerOptions{
|
|
IsLinkingAllowed: true,
|
|
IsCreationAllowed: true,
|
|
IsAutoCreation: true,
|
|
IsAutoUpdate: true,
|
|
AutoLinking: "AUTO_LINKING_OPTION_USERNAME",
|
|
},
|
|
IsIdTokenMapping: false,
|
|
UsePkce: false,
|
|
}
|
|
|
|
resp, err := doRequestWithHeaders(apiURL+"/admin/v1/idps/generic_oidc", pat, domain, createIDP)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
data, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
idp := new(idp)
|
|
if err := json.Unmarshal(data, idp); err != nil {
|
|
return "", err
|
|
}
|
|
return idp.ID, nil
|
|
}
|
|
|
|
type activateIDP struct {
|
|
IdpId string `json:"idpId"`
|
|
}
|
|
|
|
func ActivateIDP(apiURL, pat, domain string, idpID string) error {
|
|
activateIDP := &activateIDP{
|
|
IdpId: idpID,
|
|
}
|
|
_, err := doRequestWithHeaders(apiURL+"/admin/v1/policies/login/idps", pat, domain, activateIDP)
|
|
return err
|
|
}
|
|
|
|
func doRequestWithHeaders(apiURL, pat, domain string, body any) (*http.Response, error) {
|
|
data, err := json.Marshal(body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(data)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
values := http.Header{}
|
|
values.Add("Authorization", "Bearer "+pat)
|
|
values.Add("x-forwarded-host", domain)
|
|
values.Add("Content-Type", "application/json")
|
|
req.Header = values
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return resp, nil
|
|
}
|