auth-proxy: verify group membership (#105)
* auth-proxy: verify group membership
* memberships: install memberships app and use it in few apps
* app-repo: render auth
* installer: always use external dependencies option in app configs
* installer: fix auth handling
* auth-proxy: configure membership-addr and groups flags in helm chart
* installer: fix indentation
* app-manager: fix how auth block is rendered
---------
Co-authored-by: Giorgi Lekveishvili <lekva@gl-mbp-m1-max.local>
diff --git a/core/auth/proxy/Makefile b/core/auth/proxy/Makefile
index 053ab05..4ec89b0 100644
--- a/core/auth/proxy/Makefile
+++ b/core/auth/proxy/Makefile
@@ -8,21 +8,21 @@
rm -f server server_*
build: clean
- go build -o server *.go
+ /usr/local/go/bin/go build -o server *.go
build_arm64: export CGO_ENABLED=0
build_arm64: export GO111MODULE=on
build_arm64: export GOOS=linux
build_arm64: export GOARCH=arm64
build_arm64:
- go build -o server_arm64 *.go
+ /usr/local/go/bin/go build -o server_arm64 *.go
build_amd64: export CGO_ENABLED=0
build_amd64: export GO111MODULE=on
build_amd64: export GOOS=linux
build_amd64: export GOARCH=amd64
build_amd64:
- go build -o server_amd64 *.go
+ /usr/local/go/bin/go build -o server_amd64 *.go
push_arm64: clean build_arm64
$(podman) build --platform linux/arm64 --tag=$(repo_name)/auth-proxy:arm64 .
diff --git a/core/auth/proxy/go.mod b/core/auth/proxy/go.mod
new file mode 100644
index 0000000..856b8bf
--- /dev/null
+++ b/core/auth/proxy/go.mod
@@ -0,0 +1,5 @@
+module github.com/giolekva/pcloud/core/auth/proxy
+
+go 1.21.5
+
+require golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81
diff --git a/core/auth/proxy/go.sum b/core/auth/proxy/go.sum
new file mode 100644
index 0000000..76a41df
--- /dev/null
+++ b/core/auth/proxy/go.sum
@@ -0,0 +1,2 @@
+golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 h1:6R2FC06FonbXQ8pK11/PDFY6N6LWlf9KlzibaCapmqc=
+golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
diff --git a/core/auth/proxy/main.go b/core/auth/proxy/main.go
index 8b3d837..d1e1b49 100644
--- a/core/auth/proxy/main.go
+++ b/core/auth/proxy/main.go
@@ -13,11 +13,15 @@
"net/http/cookiejar"
"net/url"
"strings"
+
+ "golang.org/x/exp/slices"
)
var port = flag.Int("port", 3000, "Port to listen on")
var whoAmIAddr = flag.String("whoami-addr", "", "Kratos whoami endpoint address")
var loginAddr = flag.String("login-addr", "", "Login page address")
+var membershipAddr = flag.String("membership-addr", "", "Group membership API endpoint")
+var groups = flag.String("groups", "", "Comma separated list of groups. User must be part of at least one of them. If empty group membership will not be checked.")
var upstream = flag.String("upstream", "", "Upstream service address")
type user struct {
@@ -62,6 +66,25 @@
http.Redirect(w, r, addr, http.StatusSeeOther)
return
}
+ if *groups != "" {
+ hasPermission := false
+ tg, err := getTransitiveGroups(user.Identity.Traits.Username)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ for _, i := range strings.Split(*groups, ",") {
+ if slices.Contains(tg, strings.TrimSpace(i)) {
+ hasPermission = true
+ break
+ }
+ }
+ if !hasPermission {
+ http.Error(w, "not authorized", http.StatusUnauthorized)
+ return
+ }
+
+ }
rc := r.Clone(context.Background())
rc.Header.Add("X-User", user.Identity.Traits.Username)
ru, err := url.Parse(fmt.Sprintf("http://%s%s", *upstream, r.URL.RequestURI()))
@@ -148,8 +171,27 @@
return nil, fmt.Errorf("Unknown error: %s", tmp)
}
+type MembershipInfo struct {
+ MemberOf []string `json:"memberOf"`
+}
+
+func getTransitiveGroups(user string) ([]string, error) {
+ resp, err := http.Get(fmt.Sprintf("%s/%s", *membershipAddr, user))
+ if err != nil {
+ return nil, err
+ }
+ var info MembershipInfo
+ if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
+ return nil, err
+ }
+ return info.MemberOf, nil
+}
+
func main() {
flag.Parse()
+ if *groups != "" && *membershipAddr == "" {
+ log.Fatal("membership-addr flag is required when groups are provided")
+ }
http.HandleFunc("/", handle)
fmt.Printf("Starting HTTP server on port: %d\n", *port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
diff --git a/core/installer/app.go b/core/installer/app.go
index 8cfb2b5..938d08f 100644
--- a/core/installer/app.go
+++ b/core/installer/app.go
@@ -59,12 +59,6 @@
"values-tmpl/hydra-maester.cue",
}
-const cueBaseConfigImports = `
-import (
- "list"
-)
-`
-
// TODO(gio): import
const cueBaseConfig = `
name: string | *""
@@ -73,6 +67,11 @@
icon: string | *""
namespace: string | *""
+#Auth: {
+ enabled: bool | *false // TODO(gio): enabled by default?
+ groups: string | *"" // TODO(gio): []string
+}
+
#Network: {
name: string
ingressClass: string
@@ -142,8 +141,7 @@
#Helm: {
name: string
- dependsOn: [...#Helm] | *[]
- dependsOnExternal: [...#ResourceReference] | *[]
+ dependsOn: [...#ResourceReference] | *[]
...
}
@@ -159,8 +157,7 @@
_name: string
_chart: #Chart
_values: _
- _dependencies: [...#Helm] | *[]
- _externalDependencies: [...#ResourceReference] | *[]
+ _dependencies: [...#ResourceReference] | *[]
apiVersion: "helm.toolkit.fluxcd.io/v2beta1"
kind: "HelmRelease"
@@ -170,12 +167,7 @@
}
spec: {
interval: "1m0s"
- dependsOn: list.Concat([_externalDependencies, [
- for d in _dependencies {
- name: d.name
- namespace: release.namespace
- }
- ]])
+ dependsOn: _dependencies
chart: {
spec: _chart
}
@@ -190,7 +182,6 @@
_chart: r.chart
_values: r.values
_dependencies: r.dependsOn
- _externalDependencies: r.dependsOnExternal
}
}
}
@@ -304,12 +295,12 @@
return Rendered{}, err
}
for i.Next() {
- name := fmt.Sprintf("%s.yaml", cleanName(i.Selector().String()))
- contents, err := cueyaml.Encode(i.Value())
- if err != nil {
+ if contents, err := cueyaml.Encode(i.Value()); err != nil {
return Rendered{}, err
+ } else {
+ name := fmt.Sprintf("%s.yaml", cleanName(i.Selector().String()))
+ ret.Resources[name] = contents
}
- ret.Resources[name] = contents
}
return ret, nil
}
@@ -536,7 +527,7 @@
func processCueConfig(contents string) (*cue.Value, error) {
ctx := cuecontext.New()
- cfg := ctx.CompileString(cueBaseConfigImports + contents + cueBaseConfig)
+ cfg := ctx.CompileString(contents + cueBaseConfig)
if err := cfg.Err(); err != nil {
return nil, err
}
diff --git a/core/installer/repoio.go b/core/installer/repoio.go
index 5b90327..dbbeda9 100644
--- a/core/installer/repoio.go
+++ b/core/installer/repoio.go
@@ -397,6 +397,12 @@
return nil, err
}
ret[k] = n
+ case KindAuth:
+ r, err := deriveValues(v, AuthSchema, networks)
+ if err != nil {
+ return nil, err
+ }
+ ret[k] = r
case KindStruct:
r, err := deriveValues(v, def, networks)
if err != nil {
diff --git a/core/installer/schema.go b/core/installer/schema.go
index 249190b..a692ecf 100644
--- a/core/installer/schema.go
+++ b/core/installer/schema.go
@@ -16,6 +16,7 @@
KindString = 1
KindStruct = 2
KindNetwork = 3
+ KindAuth = 5
KindNumber = 4
)
@@ -24,6 +25,13 @@
Fields() map[string]Schema
}
+var AuthSchema Schema = structSchema{
+ fields: map[string]Schema{
+ "enabled": basicSchema{KindBoolean},
+ "groups": basicSchema{KindString},
+ },
+}
+
const networkSchema = `
#Network: {
name: string
@@ -50,6 +58,30 @@
return false
}
+const authSchema = `
+#Auth: {
+ enabled: bool | false
+ groups: string | *""
+}
+
+value: { %s }
+`
+
+func isAuth(v cue.Value) bool {
+ if v.Value().Kind() != cue.StructKind {
+ return false
+ }
+ s := fmt.Sprintf(authSchema, fmt.Sprintf("%#v", v))
+ c := cuecontext.New()
+ u := c.CompileString(s)
+ auth := u.LookupPath(cue.ParsePath("#Auth"))
+ vv := u.LookupPath(cue.ParsePath("value"))
+ if err := auth.Subsume(vv); err == nil {
+ return true
+ }
+ return false
+}
+
type basicSchema struct {
kind Kind
}
@@ -85,6 +117,8 @@
case cue.StructKind:
if isNetwork(v) {
return basicSchema{KindNetwork}, nil
+ } else if isAuth(v) {
+ return basicSchema{KindAuth}, nil
}
s := structSchema{make(map[string]Schema)}
f, err := v.Fields(cue.Schema())
diff --git a/core/installer/tasks/infra.go b/core/installer/tasks/infra.go
index 59bc986..c7e6f9e 100644
--- a/core/installer/tasks/infra.go
+++ b/core/installer/tasks/infra.go
@@ -35,6 +35,7 @@
SetupNetwork(env, startIP, st),
SetupCertificateIssuers(env, st),
SetupAuth(env, st),
+ SetupGroupMemberships(env, st),
SetupHeadscale(env, startIP, st),
SetupWelcome(env, st),
SetupAppStore(env, st),
@@ -229,6 +230,24 @@
)
}
+func SetupGroupMemberships(env Env, st *state) Task {
+ t := newLeafTask("Setup", func() error {
+ app, err := st.appsRepo.Find("memberships")
+ if err != nil {
+ return err
+ }
+ if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{}); err != nil {
+ return err
+ }
+ return nil
+ })
+ return newSequentialParentTask(
+ "Group Membership",
+ &t,
+ waitForAddr(fmt.Sprintf("https://memberships.p.%s", env.Domain)),
+ )
+}
+
func SetupHeadscale(env Env, startIP net.IP, st *state) Task {
t := newLeafTask("Setup", func() error {
app, err := st.appsRepo.Find("headscale")
diff --git a/core/installer/values-tmpl/cert-manager.cue b/core/installer/values-tmpl/cert-manager.cue
index 7f4f55a..fdede37 100644
--- a/core/installer/values-tmpl/cert-manager.cue
+++ b/core/installer/values-tmpl/cert-manager.cue
@@ -55,7 +55,7 @@
helm: {
"cert-manager": {
chart: charts.certManager
- dependsOnExternal: [{
+ dependsOn: [{
name: "ingress-public"
namespace: _ingressPublic
}]
@@ -87,7 +87,10 @@
}
"cert-manager-webhook-pcloud": {
chart: charts.dnsChallengeSolver
- dependsOn: [helm["cert-manager"]]
+ dependsOn: [{
+ name: "cert-manager"
+ namespace: release.namespace
+ }]
values: {
fullnameOverride: "\(global.pcloudEnvName)-cert-manager-webhook-pcloud"
certManager: {
diff --git a/core/installer/values-tmpl/certificate-issuer-private.cue b/core/installer/values-tmpl/certificate-issuer-private.cue
index ca4c7c2..fc490a3 100644
--- a/core/installer/values-tmpl/certificate-issuer-private.cue
+++ b/core/installer/values-tmpl/certificate-issuer-private.cue
@@ -24,7 +24,7 @@
helm: {
"certificate-issuer-private": {
chart: charts["certificate-issuer-private"]
- dependsOnExternal: [{
+ dependsOn: [{
name: "ingress-nginx"
namespace: "\(global.namespacePrefix)ingress-private"
}]
diff --git a/core/installer/values-tmpl/certificate-issuer-public.cue b/core/installer/values-tmpl/certificate-issuer-public.cue
index 33be2a9..58a4bfd 100644
--- a/core/installer/values-tmpl/certificate-issuer-public.cue
+++ b/core/installer/values-tmpl/certificate-issuer-public.cue
@@ -19,7 +19,7 @@
helm: {
"certificate-issuer-public": {
chart: charts["certificate-issuer-public"]
- dependsOnExternal: [{
+ dependsOn: [{
name: "ingress-nginx"
namespace: "\(global.namespacePrefix)ingress-private"
}]
diff --git a/core/installer/values-tmpl/core-auth.cue b/core/installer/values-tmpl/core-auth.cue
index 192f806..0e9f26f 100644
--- a/core/installer/values-tmpl/core-auth.cue
+++ b/core/installer/values-tmpl/core-auth.cue
@@ -128,10 +128,12 @@
}
auth: {
chart: charts.auth
- dependsOn: [postgres]
- dependsOnExternal: [{
+ dependsOn: [{
name: "ingress-nginx"
namespace: "\(global.namespacePrefix)ingress-private"
+ }, {
+ name: "postgres"
+ namespace: release.namespace
}]
values: {
kratos: {
diff --git a/core/installer/values-tmpl/headscale.cue b/core/installer/values-tmpl/headscale.cue
index 1db5eb8..fee75ab 100644
--- a/core/installer/values-tmpl/headscale.cue
+++ b/core/installer/values-tmpl/headscale.cue
@@ -47,7 +47,7 @@
"oauth2-client": {
chart: charts.oauth2Client
// TODO(gio): remove once hydra maester is installed as part of dodo itself
- dependsOnExternal: [{
+ dependsOn: [{
name: "auth"
namespace: "\(global.namespacePrefix)core-auth"
}]
@@ -63,7 +63,7 @@
}
headscale: {
chart: charts.headscale
- dependsOnExternal: [{
+ dependsOn: [{
name: "auth"
namespace: "\(global.namespacePrefix)core-auth"
}]
diff --git a/core/installer/values-tmpl/matrix.cue b/core/installer/values-tmpl/matrix.cue
index 9daf2eb..1f32318 100644
--- a/core/installer/values-tmpl/matrix.cue
+++ b/core/installer/values-tmpl/matrix.cue
@@ -69,9 +69,10 @@
}
}
matrix: {
- dependsOn: [
- postgres
- ]
+ dependsOn: [{
+ name: "postgres"
+ namespace: release.namespace
+ }]
chart: charts.matrix
values: {
domain: global.domain
diff --git a/core/installer/values-tmpl/memberships.cue b/core/installer/values-tmpl/memberships.cue
index 4abd6d6..6790cfd 100644
--- a/core/installer/values-tmpl/memberships.cue
+++ b/core/installer/values-tmpl/memberships.cue
@@ -1,14 +1,10 @@
-input: {
- network: #Network
- subdomain: string
- requireAuth: bool
-}
+input: {}
-_domain: "\(input.subdomain).\(input.network.domain)"
+_domain: "memberships.\(global.privateDomain)"
name: "memberships"
-namespace: "app-memberships"
-readme: "Memberships application will be installed on \(input.network.name) network and be accessible at https://\(_domain)"
+namespace: "core-auth-memberships"
+readme: "Memberships application will be installed on Private network and be accessible at https://\(_domain)"
description: "The application is a membership management system designed to facilitate the organization and administration of groups and memberships. Can be configured to be reachable only from private network or publicly."
icon: "<svg xmlns='http://www.w3.org/2000/svg' width='50' height='50' viewBox='0 0 24 24'><path fill='currentColor' d='M15.43 15.48c-1.1-.49-2.26-.73-3.43-.73c-1.18 0-2.33.25-3.43.73c-.23.1-.4.29-.49.52h7.85a.978.978 0 0 0-.5-.52m-2.49-6.69C12.86 8.33 12.47 8 12 8s-.86.33-.94.79l-.2 1.21h2.28z' opacity='0.3'/><path fill='currentColor' d='M10.27 12h3.46a1.5 1.5 0 0 0 1.48-1.75l-.3-1.79a2.951 2.951 0 0 0-5.82.01l-.3 1.79c-.15.91.55 1.74 1.48 1.74m.79-3.21c.08-.46.47-.79.94-.79s.86.33.94.79l.2 1.21h-2.28zm-9.4 2.32c-.13.26-.18.57-.1.88c.16.69.76 1.03 1.53 1h1.95c.83 0 1.51-.58 1.51-1.29c0-.14-.03-.27-.07-.4c-.01-.03-.01-.05.01-.08c.09-.16.14-.34.14-.53c0-.31-.14-.6-.36-.82c-.03-.03-.03-.06-.02-.1c.07-.2.07-.43.01-.65a1.12 1.12 0 0 0-.99-.74a.09.09 0 0 1-.07-.03C5.03 8.14 4.72 8 4.37 8c-.3 0-.57.1-.75.26c-.03.03-.06.03-.09.02a1.24 1.24 0 0 0-1.7 1.03c0 .02-.01.04-.03.06c-.29.26-.46.65-.41 1.05c.03.22.12.43.25.6c.03.02.03.06.02.09m14.58 2.54c-1.17-.52-2.61-.9-4.24-.9c-1.63 0-3.07.39-4.24.9A2.988 2.988 0 0 0 6 16.39V18h12v-1.61c0-1.18-.68-2.26-1.76-2.74M8.07 16a.96.96 0 0 1 .49-.52c1.1-.49 2.26-.73 3.43-.73c1.18 0 2.33.25 3.43.73c.23.1.4.29.49.52zm-6.85-1.42A2.01 2.01 0 0 0 0 16.43V18h4.5v-1.61c0-.83.23-1.61.63-2.29c-.37-.06-.74-.1-1.13-.1c-.99 0-1.93.21-2.78.58m21.56 0A6.95 6.95 0 0 0 20 14c-.39 0-.76.04-1.13.1c.4.68.63 1.46.63 2.29V18H24v-1.57c0-.81-.48-1.53-1.22-1.85M22 11v-.5c0-1.1-.9-2-2-2h-2c-.42 0-.65.48-.39.81l.7.63c-.19.31-.31.67-.31 1.06c0 1.1.9 2 2 2s2-.9 2-2'/></svg>"
@@ -59,7 +55,7 @@
_httpPortName: "http"
helm: {
- "memberships": {
+ memberships: {
chart: charts.memberships
values: {
storage: {
diff --git a/core/installer/values-tmpl/pihole.cue b/core/installer/values-tmpl/pihole.cue
index 35d4c51..ff04fe2 100644
--- a/core/installer/values-tmpl/pihole.cue
+++ b/core/installer/values-tmpl/pihole.cue
@@ -1,7 +1,7 @@
input: {
network: #Network
subdomain: string
- requireAuth: bool
+ auth: #Auth
}
_domain: "\(input.subdomain).\(input.network.domain)"
@@ -108,7 +108,7 @@
}
}
}
- if input.requireAuth {
+ if input.auth.enabled {
"auth-proxy": {
chart: charts.authProxy
values: {
@@ -120,6 +120,8 @@
upstream: "\(_piholeServiceName).\(release.namespace).svc.cluster.local"
whoAmIAddr: "https://accounts.\(global.domain)/sessions/whoami"
loginAddr: "https://accounts-ui.\(global.domain)/login"
+ membershipAddr: "http://memberships.\(global.id)-core-auth-memberships.svc.cluster.local/api/user"
+ groups: input.auth.groups
portName: _httpPortName
}
}
@@ -131,11 +133,11 @@
ingressClassName: input.network.ingressClass
certificateIssuer: input.network.certificateIssuer
service: {
- if input.requireAuth {
+ if input.auth.enabled {
name: _authProxyServiceName
port: name: _httpPortName
}
- if !input.requireAuth {
+ if !input.auth.enabled {
name: _piholeServiceName
port: number: _serviceWebPort
}
diff --git a/core/installer/values-tmpl/rpuppy.cue b/core/installer/values-tmpl/rpuppy.cue
index ff316a0..4955f81 100644
--- a/core/installer/values-tmpl/rpuppy.cue
+++ b/core/installer/values-tmpl/rpuppy.cue
@@ -1,7 +1,7 @@
input: {
network: #Network
subdomain: string
- requireAuth: bool
+ auth: #Auth
}
_domain: "\(input.subdomain).\(input.network.domain)"
@@ -70,7 +70,7 @@
portName: _httpPortName
}
}
- if input.requireAuth {
+ if input.auth.enabled {
"auth-proxy": {
chart: charts.authProxy
values: {
@@ -82,6 +82,8 @@
upstream: "\(_rpuppyServiceName).\(release.namespace).svc.cluster.local"
whoAmIAddr: "https://accounts.\(global.domain)/sessions/whoami"
loginAddr: "https://accounts-ui.\(global.domain)/login"
+ membershipAddr: "http://memberships.\(global.id)-core-auth-memberships.svc.cluster.local/api/user"
+ groups: input.auth.groups
portName: _httpPortName
}
}
@@ -93,10 +95,10 @@
ingressClassName: input.network.ingressClass
certificateIssuer: input.network.certificateIssuer
service: {
- if input.requireAuth {
+ if input.auth.enabled {
name: _authProxyServiceName
}
- if !input.requireAuth {
+ if !input.auth.enabled {
name: _rpuppyServiceName
}
port: name: _httpPortName
diff --git a/core/installer/values-tmpl/url-shortener.cue b/core/installer/values-tmpl/url-shortener.cue
index 7d854e8..42a3ce8 100644
--- a/core/installer/values-tmpl/url-shortener.cue
+++ b/core/installer/values-tmpl/url-shortener.cue
@@ -1,7 +1,7 @@
input: {
network: #Network
subdomain: string
- requireAuth: bool
+ auth: #Auth
}
_domain: "\(input.subdomain).\(input.network.domain)"
@@ -73,7 +73,7 @@
portName: _httpPortName
}
}
- if input.requireAuth {
+ if input.auth.enabled {
"auth-proxy": {
chart: charts.authProxy
values: {
@@ -85,6 +85,8 @@
upstream: "\(_urlShortenerServiceName).\(release.namespace).svc.cluster.local"
whoAmIAddr: "https://accounts.\(global.domain)/sessions/whoami"
loginAddr: "https://accounts-ui.\(global.domain)/login"
+ membershipAddr: "http://memberships.\(global.id)-core-auth-memberships.svc.cluster.local/api/user"
+ groups: input.auth.groups
portName: _httpPortName
}
}
@@ -96,10 +98,10 @@
ingressClassName: input.network.ingressClass
certificateIssuer: input.network.certificateIssuer
service: {
- if input.requireAuth {
+ if input.auth.enabled {
name: _authProxyServiceName
}
- if !input.requireAuth {
+ if !input.auth.enabled {
name: _urlShortenerServiceName
}
port: name: _httpPortName
diff --git a/core/installer/welcome/appmanager-tmpl/app.html b/core/installer/welcome/appmanager-tmpl/app.html
index aebbd39..088a4c5 100644
--- a/core/installer/welcome/appmanager-tmpl/app.html
+++ b/core/installer/welcome/appmanager-tmpl/app.html
@@ -7,25 +7,42 @@
<label for="{{ $name }}">
<span>{{ $name }}</span>
</label>
- <input type="checkbox" role="swtich" name="{{ $name }}" oninput="valueChanged({{ $name }}, this.checked)" {{ if $readonly }}disabled{{ end }} {{ if index $data $name }}checked{{ end }}/>
+ <input type="checkbox" role="swtich" name="{{ $name }}" oninput="valueChanged({{ $name }}, this.checked)" {{ if $readonly }}disabled{{ end }} {{ if index $data $name }}checked{{ end }} />
{{ else if eq $schema.Kind 1 }}
<label for="{{ $name }}">
<span>{{ $name }}</span>
</label>
- <input type="text" name="{{ $name }}" oninput="valueChanged({{ $name }}, this.value)" {{ if $readonly }}disabled{{ end }} value="{{ index $data $name }}"/>
+ <input type="text" name="{{ $name }}" oninput="valueChanged({{ $name }}, this.value)" {{ if $readonly }}disabled{{ end }} value="{{ index $data $name }}" />
{{ else if eq $schema.Kind 4 }}
<label for="{{ $name }}">
<span>{{ $name }}</span>
</label>
- <input type="text" name="{{ $name }}" oninput="valueChanged({{ $name }}, this.value)" {{ if $readonly }}disabled{{ end }} value="{{ index $data $name }}"/>
+ <input type="text" name="{{ $name }}" oninput="valueChanged({{ $name }}, this.value)" {{ if $readonly }}disabled{{ end }} value="{{ index $data $name }}" />
{{ else if eq $schema.Kind 3 }}
- <select oninput="valueChanged({{ $name }}, this.value)" {{ if $readonly }}disabled{{ end }} >
+ <label for="{{ $name }}">
+ <span>{{ $name }}</span>
+ </label>
+ <select name="{{ $name }}" oninput="valueChanged({{ $name }}, this.value)" {{ if $readonly }}disabled{{ end }} >
{{ if not $readonly }}<option disabled selected value> -- select an option -- </option>{{ end }}
{{ range $networks }}
<option {{if eq .Name (index $data $name) }}selected{{ end }}>{{ .Name }}</option>
{{ end }}
</select>
- {{ end }}
+ {{ else if eq $schema.Kind 5 }}
+ <label for="authEnabled">
+ <span>Require authentication</span>
+ </label>
+ {{ $auth := index $data $name }}
+ {{ $authEnabled := false }}
+ {{ $authGroups := "" }}
+ {{ if and $auth (index $auth "enabled") }}{{ $authEnabled = true }}{{ end }}
+ {{ if and $auth (index $auth "groups") }}{{ $authGroups = index $auth "groups" }}{{ end }}
+ <input type="checkbox" role="swtich" name="authEnabled" oninput="valueChanged('{{- $name -}}.enabled', this.checked)" {{ if $readonly }}disabled{{ end }} {{ if $authEnabled }}checked{{ end }} />
+ <label for="authGroups">
+ <span>Authentication Groups</span>
+ </label>
+ <input type="text" name="authGroups" oninput="valueChanged('{{- $name -}}.groups', this.value)" {{ if $readonly }}disabled{{ end }} value="{{ $authGroups }}" />
+ {{ end }}
{{ end }}
{{ end }}
@@ -105,8 +122,18 @@
let readme = "";
let config = {{ if $instance }}JSON.parse({{ toJson $instance.Config }}){{ else }}{}{{ end }};
+ function setValue(name, value, config) {
+ let items = name.split(".")
+ for (let i = 0; i < items.length - 1; i++) {
+ if (!(items[i] in config)) {
+ config[items[i]] = {}
+ }
+ config = config[items[i]];
+ }
+ config[items[items.length - 1]] = value;
+}
function valueChanged(name, value) {
- config[name] = value;
+ setValue(name, value, config);
renderReadme();
}