feat(helm): add support for multi-user BasicAuth mode

Adds multi-user-basic authentication mode to the helm chart alongside
existing basic (single-user) and oauth modes.

Multi-user BasicAuth mode enables:
- Pass-through authentication (credentials in request headers)
- Optional background operations using app passwords via Astrolabe
- Optional OAuth client credentials (uses DCR if not provided)
- Token encryption and persistent storage for background sync

Changes:
- values.yaml: Add auth.multiUserBasic configuration section
- deployment.yaml: Add ENABLE_MULTI_USER_BASIC_AUTH and related env vars
- secret.yaml: Add secret template for token encryption key and OAuth credentials
- pvc.yaml: Add PVC template for token database persistence
- _helpers.tpl: Add helper functions for secret/PVC names

Tested with:
  helm template --set auth.mode=multi-user-basic \
    --set auth.multiUserBasic.enableOfflineAccess=true \
    --set auth.multiUserBasic.tokenEncryptionKey=... \
    --set vectorSync.enabled=true

Related: Multi-user deployment support (ADR-020)
This commit is contained in:
Chris Coutinho
2025-12-22 21:03:10 +01:00
parent e49dc2bfc4
commit 04140d671e
5 changed files with 149 additions and 5 deletions
@@ -72,6 +72,28 @@ Create the name of the secret to use for basic auth
{{- end }}
{{- end }}
{{/*
Create the name of the secret to use for multi-user basic auth
*/}}
{{- define "nextcloud-mcp-server.multiUserBasicSecretName" -}}
{{- if .Values.auth.multiUserBasic.existingSecret }}
{{- .Values.auth.multiUserBasic.existingSecret }}
{{- else }}
{{- include "nextcloud-mcp-server.fullname" . }}-multi-user-basic
{{- end }}
{{- end }}
{{/*
Create the name of the PVC to use for multi-user basic token storage
*/}}
{{- define "nextcloud-mcp-server.multiUserBasicPvcName" -}}
{{- if .Values.auth.multiUserBasic.persistence.existingClaim }}
{{- .Values.auth.multiUserBasic.persistence.existingClaim }}
{{- else }}
{{- include "nextcloud-mcp-server.fullname" . }}-token-storage
{{- end }}
{{- end }}
{{/*
Create the name of the secret to use for OAuth
*/}}
@@ -68,7 +68,7 @@ spec:
- name: NEXTCLOUD_HOST
value: {{ .Values.nextcloud.host | quote }}
{{- if eq .Values.auth.mode "basic" }}
# Basic auth mode
# Basic auth mode (single-user)
- name: NEXTCLOUD_USERNAME
valueFrom:
secretKeyRef:
@@ -79,6 +79,41 @@ spec:
secretKeyRef:
name: {{ include "nextcloud-mcp-server.basicAuthSecretName" . }}
key: {{ .Values.auth.basic.passwordKey }}
{{- else if eq .Values.auth.mode "multi-user-basic" }}
# Multi-user BasicAuth mode (pass-through)
- name: ENABLE_MULTI_USER_BASIC_AUTH
value: "true"
- name: NEXTCLOUD_MCP_SERVER_URL
value: {{ include "nextcloud-mcp-server.mcpServerUrl" . | quote }}
- name: NEXTCLOUD_PUBLIC_ISSUER_URL
value: {{ include "nextcloud-mcp-server.publicIssuerUrl" . | quote }}
{{- if .Values.auth.multiUserBasic.enableOfflineAccess }}
# Background operations with app passwords
- name: ENABLE_OFFLINE_ACCESS
value: "true"
- name: TOKEN_STORAGE_DB
value: {{ .Values.auth.multiUserBasic.tokenStorageDb | quote }}
- name: TOKEN_ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: {{ include "nextcloud-mcp-server.multiUserBasicSecretName" . }}
key: {{ .Values.auth.multiUserBasic.tokenEncryptionKeyKey }}
- name: NEXTCLOUD_OIDC_SCOPES
value: {{ .Values.auth.multiUserBasic.scopes | quote }}
{{- if or .Values.auth.multiUserBasic.clientId .Values.auth.multiUserBasic.existingSecret }}
# Static OAuth credentials (optional - uses DCR if not provided)
- name: NEXTCLOUD_OIDC_CLIENT_ID
valueFrom:
secretKeyRef:
name: {{ include "nextcloud-mcp-server.multiUserBasicSecretName" . }}
key: {{ .Values.auth.multiUserBasic.clientIdKey }}
- name: NEXTCLOUD_OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: {{ include "nextcloud-mcp-server.multiUserBasicSecretName" . }}
key: {{ .Values.auth.multiUserBasic.clientSecretKey }}
{{- end }}
{{- end }}
{{- else if eq .Values.auth.mode "oauth" }}
# OAuth mode
- name: NEXTCLOUD_MCP_SERVER_URL
@@ -251,6 +286,10 @@ spec:
- name: oauth-storage
mountPath: /app/.oauth
{{- end }}
{{- if and (eq .Values.auth.mode "multi-user-basic") .Values.auth.multiUserBasic.enableOfflineAccess .Values.auth.multiUserBasic.persistence.enabled }}
- name: token-storage
mountPath: /app/data
{{- end }}
{{- if and (eq .Values.qdrant.mode "persistent") .Values.qdrant.localPersistence.enabled }}
- name: qdrant-data
mountPath: /app/data
@@ -266,6 +305,11 @@ spec:
persistentVolumeClaim:
claimName: {{ include "nextcloud-mcp-server.oauthPvcName" . }}
{{- end }}
{{- if and (eq .Values.auth.mode "multi-user-basic") .Values.auth.multiUserBasic.enableOfflineAccess .Values.auth.multiUserBasic.persistence.enabled }}
- name: token-storage
persistentVolumeClaim:
claimName: {{ include "nextcloud-mcp-server.multiUserBasicPvcName" . }}
{{- end }}
{{- if and (eq .Values.qdrant.mode "persistent") .Values.qdrant.localPersistence.enabled }}
- name: qdrant-data
persistentVolumeClaim:
@@ -16,6 +16,24 @@ spec:
storage: {{ .Values.auth.oauth.persistence.size }}
{{- end }}
---
{{- if and (eq .Values.auth.mode "multi-user-basic") .Values.auth.multiUserBasic.enableOfflineAccess .Values.auth.multiUserBasic.persistence.enabled (not .Values.auth.multiUserBasic.persistence.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "nextcloud-mcp-server.fullname" . }}-token-storage
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
spec:
accessModes:
- {{ .Values.auth.multiUserBasic.persistence.accessMode }}
{{- if .Values.auth.multiUserBasic.persistence.storageClass }}
storageClassName: {{ .Values.auth.multiUserBasic.persistence.storageClass }}
{{- end }}
resources:
requests:
storage: {{ .Values.auth.multiUserBasic.persistence.size }}
{{- end }}
---
{{- if and (eq .Values.qdrant.mode "persistent") .Values.qdrant.localPersistence.enabled (not .Values.qdrant.localPersistence.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
@@ -13,6 +13,24 @@ data:
{{- end }}
{{- end }}
---
{{- if eq .Values.auth.mode "multi-user-basic" }}
{{- if and .Values.auth.multiUserBasic.enableOfflineAccess (not .Values.auth.multiUserBasic.existingSecret) }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "nextcloud-mcp-server.fullname" . }}-multi-user-basic
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
type: Opaque
data:
{{ .Values.auth.multiUserBasic.tokenEncryptionKeyKey }}: {{ .Values.auth.multiUserBasic.tokenEncryptionKey | b64enc | quote }}
{{- if .Values.auth.multiUserBasic.clientId }}
{{ .Values.auth.multiUserBasic.clientIdKey }}: {{ .Values.auth.multiUserBasic.clientId | b64enc | quote }}
{{ .Values.auth.multiUserBasic.clientSecretKey }}: {{ .Values.auth.multiUserBasic.clientSecret | b64enc | quote }}
{{- end }}
{{- end }}
{{- end }}
---
{{- if eq .Values.auth.mode "oauth" }}
{{- if and .Values.auth.oauth.clientId (not .Values.auth.oauth.existingSecret) }}
apiVersion: v1
+46 -4
View File
@@ -33,14 +33,15 @@ nextcloud:
publicIssuerUrl: ""
# Authentication configuration
# Choose either basic auth OR oauth (not both)
# Choose one mode: "basic", "multi-user-basic", or "oauth"
auth:
# Authentication mode: "basic" or "oauth"
# basic: Uses username/password (recommended for most users)
# Authentication mode: "basic", "multi-user-basic", or "oauth"
# basic: Single-user with username/password (recommended for personal use)
# multi-user-basic: Multi-user with BasicAuth pass-through (credentials in request headers)
# oauth: Uses OAuth2/OIDC (experimental, requires patches)
mode: basic
# Basic authentication settings
# Basic authentication settings (single-user mode)
basic:
# Nextcloud username (ignored if existingSecret is set)
username: ""
@@ -58,6 +59,47 @@ auth:
usernameKey: "username"
passwordKey: "password"
# Multi-user BasicAuth settings (pass-through mode)
# Users provide credentials in request headers (Authorization: Basic ...)
# Server optionally stores app passwords for background operations
multiUserBasic:
# Enable offline access (background operations using app passwords via Astrolabe)
# When enabled, requires token encryption key and OAuth client credentials
enableOfflineAccess: false
# Token encryption key (required if enableOfflineAccess: true, ignored if existingSecret is set)
# Generate with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
tokenEncryptionKey: ""
# Token storage database path
tokenStorageDb: "/app/data/tokens.db"
# OAuth client credentials (optional - uses Dynamic Client Registration if not provided)
# Only needed if enableOfflineAccess: true
clientId: ""
clientSecret: ""
# OAuth scopes to request (space-separated)
scopes: "openid profile email offline_access notes:read notes:write calendar:read calendar:write contacts:read contacts:write cookbook:read cookbook:write deck:read deck:write tables:read tables:write files:read files:write sharing:read sharing:write todo:read todo:write"
# Use existing secret for multi-user basic auth credentials
# If set, tokenEncryptionKey, clientId, and clientSecret above are ignored
# Secret should contain keys specified in the *Key fields below
# Example:
# kubectl create secret generic my-multiuser-creds \
# --from-literal=token_encryption_key=ESF1BvEQ... \
# --from-literal=client_id=my-client-id \
# --from-literal=client_secret=my-client-secret
existingSecret: ""
# Keys in the existing secret
tokenEncryptionKeyKey: "token_encryption_key"
clientIdKey: "client_id"
clientSecretKey: "client_secret"
# Persistent storage for token database
persistence:
enabled: true
# Storage class (leave empty for default)
storageClass: ""
accessMode: ReadWriteOnce
size: 100Mi
# Use existing PVC
existingClaim: ""
# OAuth2/OIDC settings (experimental)
oauth:
# OAuth token type: "jwt" or "opaque"