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())
+ }()
}