core-auth: api to create new identities
diff --git a/charts/auth/templates/ui.yaml b/charts/auth/templates/ui.yaml
index e16434d..e3eb4a6 100644
--- a/charts/auth/templates/ui.yaml
+++ b/charts/auth/templates/ui.yaml
@@ -13,6 +13,21 @@
     targetPort: http
     protocol: TCP
 ---
+apiVersion: v1
+kind: Service
+metadata:
+  name: api
+  namespace: {{ .Release.Namespace }}
+spec:
+  type: ClusterIP
+  selector:
+    app: ui
+  ports:
+  - name: http
+    port: 80
+    targetPort: api
+    protocol: TCP
+---
 apiVersion: networking.k8s.io/v1
 kind: Ingress
 metadata:
@@ -65,9 +80,14 @@
         - name: http
           containerPort: 8080
           protocol: TCP
+        - name: api
+          containerPort: 8081
+          protocol: TCP
         command:
         - server
         - --port=8080
         - --kratos=https://accounts.{{ .Values.ui.domain }}
         - --hydra={{ .Values.ui.hydra }}
         - --email-domain={{ .Values.ui.domain }}
+        - --api-port=8081
+        - --kratos-api=http://kratos-admin.{{ .Release.Namespace }}.svc.cluster.local
diff --git a/core/auth/ui/api.go b/core/auth/ui/api.go
new file mode 100644
index 0000000..6d14899
--- /dev/null
+++ b/core/auth/ui/api.go
@@ -0,0 +1,71 @@
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/gorilla/mux"
+)
+
+type APIServer struct {
+	r          *mux.Router
+	serv       *http.Server
+	kratosAddr string
+}
+
+func NewAPIServer(port int, kratosAddr string) *APIServer {
+	r := mux.NewRouter()
+	serv := &http.Server{
+		Addr:    fmt.Sprintf(":%d", port),
+		Handler: r,
+	}
+	return &APIServer{r, serv, kratosAddr}
+}
+
+func (s *APIServer) Start() error {
+	s.r.Path("/identities").Methods(http.MethodPost).HandlerFunc(s.identityCreate)
+	return s.serv.ListenAndServe()
+}
+
+const identityCreateTmpl = `
+{
+  "credentials": {
+    "password": {
+      "config": {
+        "password": "%s"
+      }
+    }
+  },
+  "schema_id": "user",
+  "state": "active",
+  "traits": {
+    "username": "%s"
+  }
+}
+`
+
+type identityCreateReq struct {
+	Username string `json:"username,omitempty"`
+	Password string `json:"password,omitempty"`
+}
+
+func (s *APIServer) identityCreate(w http.ResponseWriter, r *http.Request) {
+	var req identityCreateReq
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		http.Error(w, "request can not be parsed", http.StatusBadRequest)
+		return
+	}
+	var buf bytes.Buffer
+	fmt.Fprintf(&buf, identityCreateTmpl, req.Password, req.Username)
+	resp, err := http.Post(s.identitiesEndpoint(), "application/json", &buf)
+	if err != nil || resp.StatusCode != http.StatusCreated {
+		http.Error(w, "failed", http.StatusInternalServerError)
+		return
+	}
+}
+
+func (s *APIServer) identitiesEndpoint() string {
+	return fmt.Sprintf("%s/admin/identities", s.kratosAddr)
+}
diff --git a/core/auth/ui/main.go b/core/auth/ui/main.go
index e615ea8..1b5f41a 100644
--- a/core/auth/ui/main.go
+++ b/core/auth/ui/main.go
@@ -25,6 +25,9 @@
 var hydra = flag.String("hydra", "hydra.pcloud", "Hydra admin server address")
 var emailDomain = flag.String("email-domain", "lekva.me", "Email domain")
 
+var apiPort = flag.Int("api-port", 8081, "API Port to listen on")
+var kratosAPI = flag.String("kratos-api", "", "Kratos API address")
+
 var ErrNotLoggedIn = errors.New("Not logged in")
 
 //go:embed templates/*
@@ -72,11 +75,22 @@
 }
 
 type Server struct {
+	r      *mux.Router
+	serv   *http.Server
 	kratos string
 	hydra  *HydraClient
 	tmpls  *Templates
 }
 
+func NewServer(port int, kratos string, hydra *HydraClient, tmpls *Templates) *Server {
+	r := mux.NewRouter()
+	serv := &http.Server{
+		Addr:    fmt.Sprintf(":%d", port),
+		Handler: r,
+	}
+	return &Server{r, serv, kratos, hydra, tmpls}
+}
+
 func cacheControlWrapper(h http.Handler) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		// TODO(giolekva): enable caching
@@ -85,22 +99,19 @@
 	})
 }
 
-func (s *Server) Start(port int) error {
-	r := mux.NewRouter()
-	http.Handle("/", r)
+func (s *Server) Start() error {
 	var staticFS = http.FS(static)
 	fs := http.FileServer(staticFS)
-	r.PathPrefix("/static/").Handler(cacheControlWrapper(fs))
-	r.Path("/register").Methods(http.MethodGet).HandlerFunc(s.registerInitiate)
-	r.Path("/register").Methods(http.MethodPost).HandlerFunc(s.register)
-	r.Path("/login").Methods(http.MethodGet).HandlerFunc(s.loginInitiate)
-	r.Path("/login").Methods(http.MethodPost).HandlerFunc(s.login)
-	r.Path("/consent").Methods(http.MethodGet).HandlerFunc(s.consent)
-	r.Path("/consent").Methods(http.MethodPost).HandlerFunc(s.processConsent)
-	r.Path("/logout").Methods(http.MethodGet).HandlerFunc(s.logout)
-	r.Path("/").HandlerFunc(s.whoami)
-	fmt.Printf("Starting HTTP server on port: %d\n", port)
-	return http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
+	s.r.PathPrefix("/static/").Handler(cacheControlWrapper(fs))
+	s.r.Path("/register").Methods(http.MethodGet).HandlerFunc(s.registerInitiate)
+	s.r.Path("/register").Methods(http.MethodPost).HandlerFunc(s.register)
+	s.r.Path("/login").Methods(http.MethodGet).HandlerFunc(s.loginInitiate)
+	s.r.Path("/login").Methods(http.MethodPost).HandlerFunc(s.login)
+	s.r.Path("/consent").Methods(http.MethodGet).HandlerFunc(s.consent)
+	s.r.Path("/consent").Methods(http.MethodPost).HandlerFunc(s.processConsent)
+	s.r.Path("/logout").Methods(http.MethodGet).HandlerFunc(s.logout)
+	s.r.Path("/").HandlerFunc(s.whoami)
+	return s.serv.ListenAndServe()
 }
 
 func getCSRFToken(flowType, flow string, cookies []*http.Cookie) (string, error) {
@@ -492,10 +503,17 @@
 	if err != nil {
 		log.Fatal(err)
 	}
-	s := &Server{
-		kratos: *kratos,
-		hydra:  NewHydraClient(*hydra),
-		tmpls:  t,
-	}
-	log.Fatal(s.Start(*port))
+	go func() {
+		s := NewAPIServer(*apiPort, *kratosAPI)
+		log.Fatal(s.Start())
+	}()
+	func() {
+		s := NewServer(
+			*port,
+			*kratos,
+			NewHydraClient(*hydra),
+			t,
+		)
+		log.Fatal(s.Start())
+	}()
 }