mirror of
https://github.com/btouchard/ackify-ce.git
synced 2026-01-05 21:20:06 -06:00
feat(admin): add option to restrict document creation to admins only
Add new configuration option ACKIFY_ONLY_ADMIN_CAN_CREATE (default: false) to control who can create documents. Backend changes: - Add OnlyAdminCanCreate config field to AppConfig - Implement authorization checks in document handlers - Protect POST /documents and GET /documents/find-or-create endpoints - Add unit tests for admin-only document creation (4 tests) Frontend changes: - Inject ACKIFY_ONLY_ADMIN_CAN_CREATE to window object - Hide DocumentForm component for non-admin users when enabled - Add i18n translations (en, fr, es, de, it) - Display warning message for non-admin users Documentation: - Update .env.example files with new variable - Update configuration docs (en/fr) - Update install script to prompt for restriction option - Update install/README.md When enabled, only users listed in ACKIFY_ADMIN_EMAILS can create new documents. Both direct creation and find-or-create endpoints are protected.
This commit is contained in:
@@ -65,5 +65,8 @@ ACKIFY_ED25519_PRIVATE_KEY=your_base64_encoded_ed25519_private_key
|
||||
# Admin Configuration
|
||||
# ACKIFY_ADMIN_EMAILS=admin@your-domain.com,admin2@your-domain.com
|
||||
|
||||
# Document Creation Restriction
|
||||
# ACKIFY_ONLY_ADMIN_CAN_CREATE=false
|
||||
|
||||
# Server Configuration
|
||||
ACKIFY_LISTEN_ADDR=:8080
|
||||
@@ -27,10 +27,11 @@ type AuthConfig struct {
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
BaseURL string
|
||||
Organisation string
|
||||
SecureCookies bool
|
||||
AdminEmails []string
|
||||
BaseURL string
|
||||
Organisation string
|
||||
SecureCookies bool
|
||||
AdminEmails []string
|
||||
OnlyAdminCanCreate bool
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
@@ -167,6 +168,9 @@ func Load() (*Config, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Parse admin-only document creation flag
|
||||
config.App.OnlyAdminCanCreate = getEnvBool("ACKIFY_ONLY_ADMIN_CAN_CREATE", false)
|
||||
|
||||
// Parse mail config (optional, service disabled if MAIL_HOST not set)
|
||||
mailHost := getEnv("ACKIFY_MAIL_HOST", "")
|
||||
if mailHost != "" {
|
||||
|
||||
@@ -30,20 +30,33 @@ type webhookPublisher interface {
|
||||
|
||||
// Handler handles document API requests
|
||||
type Handler struct {
|
||||
signatureService *services.SignatureService
|
||||
documentService documentService
|
||||
webhookPublisher webhookPublisher
|
||||
signatureService *services.SignatureService
|
||||
documentService documentService
|
||||
webhookPublisher webhookPublisher
|
||||
adminEmails []string
|
||||
onlyAdminCanCreate bool
|
||||
}
|
||||
|
||||
// NewHandler creates a new documents handler
|
||||
// Backward-compatible constructor used by tests and existing code
|
||||
func NewHandler(signatureService *services.SignatureService, documentService documentService) *Handler {
|
||||
return &Handler{signatureService: signatureService, documentService: documentService}
|
||||
return &Handler{
|
||||
signatureService: signatureService,
|
||||
documentService: documentService,
|
||||
adminEmails: []string{},
|
||||
onlyAdminCanCreate: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Extended constructor with webhook publisher
|
||||
func NewHandlerWithPublisher(signatureService *services.SignatureService, documentService documentService, publisher webhookPublisher) *Handler {
|
||||
return &Handler{signatureService: signatureService, documentService: documentService, webhookPublisher: publisher}
|
||||
func NewHandlerWithPublisher(signatureService *services.SignatureService, documentService documentService, publisher webhookPublisher, adminEmails []string, onlyAdminCanCreate bool) *Handler {
|
||||
return &Handler{
|
||||
signatureService: signatureService,
|
||||
documentService: documentService,
|
||||
webhookPublisher: publisher,
|
||||
adminEmails: adminEmails,
|
||||
onlyAdminCanCreate: onlyAdminCanCreate,
|
||||
}
|
||||
}
|
||||
|
||||
// DocumentDTO represents a document data transfer object
|
||||
@@ -89,6 +102,34 @@ type CreateDocumentResponse struct {
|
||||
func (h *Handler) HandleCreateDocument(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
// Check if only admins can create documents
|
||||
if h.onlyAdminCanCreate {
|
||||
user, authenticated := shared.GetUserFromContext(ctx)
|
||||
if !authenticated {
|
||||
logger.Logger.Warn("Unauthenticated user attempted to create document",
|
||||
"remote_addr", r.RemoteAddr)
|
||||
shared.WriteError(w, http.StatusUnauthorized, shared.ErrCodeUnauthorized, "Authentication required to create document", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user is admin
|
||||
isAdmin := false
|
||||
for _, adminEmail := range h.adminEmails {
|
||||
if strings.ToLower(user.Email) == strings.ToLower(adminEmail) {
|
||||
isAdmin = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isAdmin {
|
||||
logger.Logger.Warn("Non-admin user attempted to create document",
|
||||
"user_email", user.Email,
|
||||
"remote_addr", r.RemoteAddr)
|
||||
shared.WriteError(w, http.StatusForbidden, shared.ErrCodeForbidden, "Only administrators can create documents", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Parse request body
|
||||
var req CreateDocumentRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
@@ -347,6 +388,26 @@ func (h *Handler) HandleFindOrCreateDocument(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
// Check if only admins can create documents
|
||||
if h.onlyAdminCanCreate {
|
||||
isAdmin := false
|
||||
for _, adminEmail := range h.adminEmails {
|
||||
if strings.ToLower(user.Email) == strings.ToLower(adminEmail) {
|
||||
isAdmin = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isAdmin {
|
||||
logger.Logger.Warn("Non-admin user attempted to create document via find-or-create",
|
||||
"user_email", user.Email,
|
||||
"reference", ref,
|
||||
"remote_addr", r.RemoteAddr)
|
||||
shared.WriteError(w, http.StatusForbidden, shared.ErrCodeForbidden, "Only administrators can create documents", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// User is authenticated, create the document
|
||||
doc, isNew, err := h.documentService.FindOrCreateDocument(ctx, ref)
|
||||
if err != nil {
|
||||
|
||||
@@ -702,6 +702,138 @@ func TestHandler_HandleCreateDocument_Concurrent(t *testing.T) {
|
||||
assert.Equal(t, 0, errCount, "All concurrent requests should succeed")
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ADMIN-ONLY DOCUMENT CREATION TESTS
|
||||
// ============================================================================
|
||||
|
||||
func TestHandler_HandleCreateDocument_AdminOnlyEnabled_AdminUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := &Handler{
|
||||
signatureService: &services.SignatureService{},
|
||||
documentService: &mockDocumentService{},
|
||||
adminEmails: []string{"admin@example.com"},
|
||||
onlyAdminCanCreate: true,
|
||||
}
|
||||
|
||||
reqBody := CreateDocumentRequest{
|
||||
Reference: "https://example.com/doc.pdf",
|
||||
Title: "Admin Document",
|
||||
}
|
||||
body, err := json.Marshal(reqBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/documents", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Add admin user to context
|
||||
adminUser := &models.User{
|
||||
Sub: "oauth2|admin",
|
||||
Email: "admin@example.com",
|
||||
Name: "Admin User",
|
||||
}
|
||||
ctx := context.WithValue(req.Context(), shared.ContextKeyUser, adminUser)
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleCreateDocument(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusCreated, rec.Code)
|
||||
assert.Contains(t, rec.Body.String(), "test-doc-123")
|
||||
}
|
||||
|
||||
func TestHandler_HandleCreateDocument_AdminOnlyEnabled_NonAdminUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := &Handler{
|
||||
signatureService: &services.SignatureService{},
|
||||
documentService: &mockDocumentService{},
|
||||
adminEmails: []string{"admin@example.com"},
|
||||
onlyAdminCanCreate: true,
|
||||
}
|
||||
|
||||
reqBody := CreateDocumentRequest{
|
||||
Reference: "https://example.com/doc.pdf",
|
||||
Title: "User Document",
|
||||
}
|
||||
body, err := json.Marshal(reqBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/documents", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Add non-admin user to context
|
||||
regularUser := &models.User{
|
||||
Sub: "oauth2|user",
|
||||
Email: "user@example.com",
|
||||
Name: "Regular User",
|
||||
}
|
||||
ctx := context.WithValue(req.Context(), shared.ContextKeyUser, regularUser)
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleCreateDocument(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusForbidden, rec.Code)
|
||||
assert.Contains(t, rec.Body.String(), "Only administrators can create documents")
|
||||
}
|
||||
|
||||
func TestHandler_HandleCreateDocument_AdminOnlyEnabled_Unauthenticated(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := &Handler{
|
||||
signatureService: &services.SignatureService{},
|
||||
documentService: &mockDocumentService{},
|
||||
adminEmails: []string{"admin@example.com"},
|
||||
onlyAdminCanCreate: true,
|
||||
}
|
||||
|
||||
reqBody := CreateDocumentRequest{
|
||||
Reference: "https://example.com/doc.pdf",
|
||||
Title: "Unauthenticated Document",
|
||||
}
|
||||
body, err := json.Marshal(reqBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/documents", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
// No user in context (unauthenticated)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleCreateDocument(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusUnauthorized, rec.Code)
|
||||
assert.Contains(t, rec.Body.String(), "Authentication required")
|
||||
}
|
||||
|
||||
func TestHandler_HandleCreateDocument_AdminOnlyDisabled_AnyUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := &Handler{
|
||||
signatureService: &services.SignatureService{},
|
||||
documentService: &mockDocumentService{},
|
||||
adminEmails: []string{"admin@example.com"},
|
||||
onlyAdminCanCreate: false, // Disabled
|
||||
}
|
||||
|
||||
reqBody := CreateDocumentRequest{
|
||||
Reference: "https://example.com/doc.pdf",
|
||||
Title: "Public Document",
|
||||
}
|
||||
body, err := json.Marshal(reqBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/documents", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
// No authentication needed when admin-only is disabled
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleCreateDocument(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusCreated, rec.Code)
|
||||
assert.Contains(t, rec.Body.String(), "test-doc-123")
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// BENCHMARKS
|
||||
// ============================================================================
|
||||
|
||||
@@ -40,6 +40,7 @@ type RouterConfig struct {
|
||||
AutoLogin bool
|
||||
OAuthEnabled bool
|
||||
MagicLinkEnabled bool
|
||||
OnlyAdminCanCreate bool
|
||||
}
|
||||
|
||||
// NewRouter creates and configures the API v1 router
|
||||
@@ -68,7 +69,7 @@ func NewRouter(cfg RouterConfig) *chi.Mux {
|
||||
healthHandler := health.NewHandler()
|
||||
authHandler := apiAuth.NewHandler(cfg.AuthService, cfg.MagicLinkService, apiMiddleware, cfg.BaseURL, cfg.OAuthEnabled, cfg.MagicLinkEnabled)
|
||||
usersHandler := users.NewHandler(cfg.AdminEmails)
|
||||
documentsHandler := documents.NewHandlerWithPublisher(cfg.SignatureService, cfg.DocumentService, cfg.WebhookPublisher)
|
||||
documentsHandler := documents.NewHandlerWithPublisher(cfg.SignatureService, cfg.DocumentService, cfg.WebhookPublisher, cfg.AdminEmails, cfg.OnlyAdminCanCreate)
|
||||
signaturesHandler := signatures.NewHandlerWithDeps(cfg.SignatureService, cfg.ExpectedSignerRepository, cfg.WebhookPublisher)
|
||||
|
||||
// Public routes
|
||||
|
||||
@@ -184,13 +184,14 @@ func NewServer(ctx context.Context, cfg *config.Config, frontend embed.FS, versi
|
||||
AutoLogin: cfg.OAuth.AutoLogin,
|
||||
OAuthEnabled: cfg.Auth.OAuthEnabled,
|
||||
MagicLinkEnabled: cfg.Auth.MagicLinkEnabled,
|
||||
OnlyAdminCanCreate: cfg.App.OnlyAdminCanCreate,
|
||||
}
|
||||
apiRouter := api.NewRouter(apiConfig)
|
||||
router.Mount("/api/v1", apiRouter)
|
||||
|
||||
router.Get("/oembed", handlers.HandleOEmbed(cfg.App.BaseURL))
|
||||
|
||||
router.NotFound(EmbedFolder(frontend, "web/dist", cfg.App.BaseURL, version, cfg.Auth.OAuthEnabled, cfg.Auth.MagicLinkEnabled, signatureRepo))
|
||||
router.NotFound(EmbedFolder(frontend, "web/dist", cfg.App.BaseURL, version, cfg.Auth.OAuthEnabled, cfg.Auth.MagicLinkEnabled, cfg.App.OnlyAdminCanCreate, signatureRepo))
|
||||
|
||||
httpServer := &http.Server{
|
||||
Addr: cfg.Server.ListenAddr,
|
||||
|
||||
@@ -22,8 +22,9 @@ import (
|
||||
// For index.html, it replaces __ACKIFY_BASE_URL__ placeholder with the actual base URL,
|
||||
// __ACKIFY_VERSION__ with the application version,
|
||||
// __ACKIFY_OAUTH_ENABLED__ and __ACKIFY_MAGICLINK_ENABLED__ with auth method flags,
|
||||
// __ACKIFY_ONLY_ADMIN_CAN_CREATE__ with document creation restriction flag,
|
||||
// and __META_TAGS__ with dynamic meta tags based on query parameters
|
||||
func EmbedFolder(fsEmbed embed.FS, targetPath string, baseURL string, version string, oauthEnabled bool, magicLinkEnabled bool, signatureRepo *database.SignatureRepository) http.HandlerFunc {
|
||||
func EmbedFolder(fsEmbed embed.FS, targetPath string, baseURL string, version string, oauthEnabled bool, magicLinkEnabled bool, onlyAdminCanCreate bool, signatureRepo *database.SignatureRepository) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
fsys, err := fs.Sub(fsEmbed, targetPath)
|
||||
if err != nil {
|
||||
@@ -62,7 +63,7 @@ func EmbedFolder(fsEmbed embed.FS, targetPath string, baseURL string, version st
|
||||
defer file.Close()
|
||||
|
||||
if shouldServeIndex || strings.HasSuffix(cleanPath, "index.html") {
|
||||
serveIndexTemplate(w, r, file, baseURL, version, oauthEnabled, magicLinkEnabled, signatureRepo)
|
||||
serveIndexTemplate(w, r, file, baseURL, version, oauthEnabled, magicLinkEnabled, onlyAdminCanCreate, signatureRepo)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -71,7 +72,7 @@ func EmbedFolder(fsEmbed embed.FS, targetPath string, baseURL string, version st
|
||||
}
|
||||
}
|
||||
|
||||
func serveIndexTemplate(w http.ResponseWriter, r *http.Request, file fs.File, baseURL string, version string, oauthEnabled bool, magicLinkEnabled bool, signatureRepo *database.SignatureRepository) {
|
||||
func serveIndexTemplate(w http.ResponseWriter, r *http.Request, file fs.File, baseURL string, version string, oauthEnabled bool, magicLinkEnabled bool, onlyAdminCanCreate bool, signatureRepo *database.SignatureRepository) {
|
||||
content, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
logger.Logger.Error("Failed to read index.html", "error", err.Error())
|
||||
@@ -91,9 +92,14 @@ func serveIndexTemplate(w http.ResponseWriter, r *http.Request, file fs.File, ba
|
||||
if magicLinkEnabled {
|
||||
magicLinkEnabledStr = "true"
|
||||
}
|
||||
onlyAdminCanCreateStr := "false"
|
||||
if onlyAdminCanCreate {
|
||||
onlyAdminCanCreateStr = "true"
|
||||
}
|
||||
|
||||
processedContent = strings.ReplaceAll(processedContent, "__ACKIFY_OAUTH_ENABLED__", oauthEnabledStr)
|
||||
processedContent = strings.ReplaceAll(processedContent, "__ACKIFY_MAGICLINK_ENABLED__", magicLinkEnabledStr)
|
||||
processedContent = strings.ReplaceAll(processedContent, "__ACKIFY_ONLY_ADMIN_CAN_CREATE__", onlyAdminCanCreateStr)
|
||||
|
||||
metaTags := generateMetaTags(r, baseURL, signatureRepo)
|
||||
processedContent = strings.ReplaceAll(processedContent, "__META_TAGS__", metaTags)
|
||||
|
||||
@@ -61,6 +61,9 @@ ACKIFY_OAUTH_SCOPES=openid,email,profile
|
||||
```bash
|
||||
# Admin email list (comma-separated)
|
||||
ACKIFY_ADMIN_EMAILS=admin@company.com,admin2@company.com
|
||||
|
||||
# Restrict document creation to admins only (default: false)
|
||||
ACKIFY_ONLY_ADMIN_CAN_CREATE=false
|
||||
```
|
||||
|
||||
Admins have access to:
|
||||
@@ -70,6 +73,11 @@ Admins have access to:
|
||||
- Email reminders sending
|
||||
- Document deletion
|
||||
|
||||
When `ACKIFY_ONLY_ADMIN_CAN_CREATE` is enabled:
|
||||
- ✅ Only admin users can create new documents
|
||||
- ✅ Non-admin users will see an error message when attempting to create documents
|
||||
- ✅ Both API endpoints (`POST /documents` and `GET /documents/find-or-create`) are protected
|
||||
|
||||
### Document Checksum (Optional)
|
||||
|
||||
Configuration for automatic checksum computation when creating documents from URLs:
|
||||
|
||||
@@ -61,6 +61,9 @@ ACKIFY_OAUTH_SCOPES=openid,email,profile
|
||||
```bash
|
||||
# Liste d'emails admin (séparés par virgules)
|
||||
ACKIFY_ADMIN_EMAILS=admin@company.com,admin2@company.com
|
||||
|
||||
# Restreindre la création de documents aux admins uniquement (défaut: false)
|
||||
ACKIFY_ONLY_ADMIN_CAN_CREATE=false
|
||||
```
|
||||
|
||||
Les admins ont accès à :
|
||||
@@ -70,6 +73,11 @@ Les admins ont accès à :
|
||||
- Envoi de rappels email
|
||||
- Suppression de documents
|
||||
|
||||
Quand `ACKIFY_ONLY_ADMIN_CAN_CREATE` est activé :
|
||||
- ✅ Seuls les utilisateurs admin peuvent créer de nouveaux documents
|
||||
- ✅ Les utilisateurs non-admin verront un message d'erreur lors d'une tentative de création
|
||||
- ✅ Les deux endpoints API (`POST /documents` et `GET /documents/find-or-create`) sont protégés
|
||||
|
||||
### Checksums Documents (Optionnel)
|
||||
|
||||
Configuration pour le calcul automatique de checksum lors de la création de documents depuis des URLs :
|
||||
|
||||
@@ -107,6 +107,10 @@ ACKIFY_OAUTH_CLIENT_SECRET=your_oauth_client_secret
|
||||
# Admins have access to document management and reminder features
|
||||
# ACKIFY_ADMIN_EMAILS=admin@your-domain.com,admin2@your-domain.com
|
||||
|
||||
# Document Creation Restriction
|
||||
# When enabled, only admins can create new documents
|
||||
# ACKIFY_ONLY_ADMIN_CAN_CREATE=false
|
||||
|
||||
# ==========================================
|
||||
# CONFIGURATION INSTRUCTIONS
|
||||
# ==========================================
|
||||
|
||||
@@ -192,6 +192,7 @@ ADMIN_EMAILS=admin@your-domain.com
|
||||
- `OAUTH_AUTO_LOGIN` - Automatically log in if OAuth session exists
|
||||
- `MAIL_*` - SMTP configuration for email features
|
||||
- `AUTH_MAGICLINK_ENABLED` - Force enable/disable MagicLink
|
||||
- `ONLY_ADMIN_CAN_CREATE` - Restrict document creation to admins only (default: false)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -328,6 +328,21 @@ done
|
||||
print_success "Admin users configured: $ADMIN_EMAILS"
|
||||
echo ""
|
||||
|
||||
# Document Creation Restriction
|
||||
echo ""
|
||||
print_info "By default, any authenticated user can create documents."
|
||||
print_info "You can restrict document creation to admins only."
|
||||
echo ""
|
||||
|
||||
ONLY_ADMIN_CAN_CREATE=false
|
||||
if prompt_yes_no "Restrict document creation to admins only?" "n"; then
|
||||
ONLY_ADMIN_CAN_CREATE=true
|
||||
print_success "Document creation restricted to admins"
|
||||
else
|
||||
print_success "All authenticated users can create documents"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ==========================================
|
||||
# Generate Secrets
|
||||
# ==========================================
|
||||
@@ -462,6 +477,9 @@ if [ -n "$ADMIN_EMAILS" ]; then
|
||||
# ==========================================
|
||||
ACKIFY_ADMIN_EMAILS=${ADMIN_EMAILS}
|
||||
|
||||
# Restrict document creation to admins only (default: false)
|
||||
ACKIFY_ONLY_ADMIN_CAN_CREATE=${ONLY_ADMIN_CAN_CREATE}
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
@@ -514,6 +532,11 @@ fi
|
||||
echo ""
|
||||
|
||||
print_success "Admin Users: ${ADMIN_EMAILS}"
|
||||
if [ "$ONLY_ADMIN_CAN_CREATE" = true ]; then
|
||||
print_info "Document Creation: Restricted to admins only"
|
||||
else
|
||||
print_info "Document Creation: All authenticated users"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
if [ "$ENABLE_TRAEFIK" = true ]; then
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
window.ACKIFY_VERSION = '__ACKIFY_VERSION__';
|
||||
window.ACKIFY_OAUTH_ENABLED = '__ACKIFY_OAUTH_ENABLED__' === 'true';
|
||||
window.ACKIFY_MAGICLINK_ENABLED = '__ACKIFY_MAGICLINK_ENABLED__' === 'true';
|
||||
window.ACKIFY_ONLY_ADMIN_CAN_CREATE = '__ACKIFY_ONLY_ADMIN_CAN_CREATE__' === 'true';
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -75,6 +75,9 @@
|
||||
"loadFailed": "Fehler beim Laden des Dokuments",
|
||||
"loginButton": "Anmelden"
|
||||
},
|
||||
"documentCreation": {
|
||||
"restrictedToAdmins": "Nur Administratoren können Dokumente erstellen."
|
||||
},
|
||||
"document": {
|
||||
"title": "Zu bestätigendes Dokument",
|
||||
"id": "ID"
|
||||
|
||||
@@ -75,6 +75,9 @@
|
||||
"loadFailed": "Failed to load document",
|
||||
"loginButton": "Sign in"
|
||||
},
|
||||
"documentCreation": {
|
||||
"restrictedToAdmins": "Only administrators can create documents."
|
||||
},
|
||||
"document": {
|
||||
"title": "Document to confirm",
|
||||
"id": "ID"
|
||||
|
||||
@@ -75,6 +75,9 @@
|
||||
"loadFailed": "Error al cargar el documento",
|
||||
"loginButton": "Iniciar sesión"
|
||||
},
|
||||
"documentCreation": {
|
||||
"restrictedToAdmins": "Solo los administradores pueden crear documentos."
|
||||
},
|
||||
"document": {
|
||||
"title": "Documento a confirmar",
|
||||
"id": "ID"
|
||||
|
||||
@@ -75,6 +75,9 @@
|
||||
"loadFailed": "Échec du chargement du document",
|
||||
"loginButton": "Se connecter"
|
||||
},
|
||||
"documentCreation": {
|
||||
"restrictedToAdmins": "Seuls les administrateurs peuvent créer des documents."
|
||||
},
|
||||
"document": {
|
||||
"title": "Document à confirmer",
|
||||
"id": "ID"
|
||||
|
||||
@@ -75,6 +75,9 @@
|
||||
"loadFailed": "Caricamento del documento fallito",
|
||||
"loginButton": "Accedi"
|
||||
},
|
||||
"documentCreation": {
|
||||
"restrictedToAdmins": "Solo gli amministratori possono creare documenti."
|
||||
},
|
||||
"document": {
|
||||
"title": "Documento da confermare",
|
||||
"id": "ID"
|
||||
|
||||
@@ -35,6 +35,11 @@ const signatureStore = useSignatureStore()
|
||||
|
||||
const docId = ref<string | undefined>(undefined)
|
||||
const user = computed(() => authStore.user)
|
||||
const isAdmin = computed(() => authStore.isAdmin)
|
||||
|
||||
// Check if document creation is restricted to admins
|
||||
const onlyAdminCanCreate = (window as any).ACKIFY_ONLY_ADMIN_CAN_CREATE || false
|
||||
const canCreateDocument = computed(() => !onlyAdminCanCreate || isAdmin.value)
|
||||
const currentDocument = ref<FindOrCreateDocumentResponse | null>(null)
|
||||
|
||||
const documentSignatures = ref<any[]>([])
|
||||
@@ -295,7 +300,15 @@ onMounted(async () => {
|
||||
<code class="block px-3 py-2 bg-muted rounded text-xs">/?doc=https://example.com/policy.pdf</code>
|
||||
<code class="block px-3 py-2 bg-muted rounded text-xs">/?doc=/path/to/document</code>
|
||||
<code class="block px-3 py-2 bg-muted rounded text-xs">/?doc=my-unique-ref</code>
|
||||
<DocumentForm />
|
||||
<DocumentForm v-if="canCreateDocument" />
|
||||
<Alert v-else variant="warning" class="mt-4">
|
||||
<div class="flex items-start">
|
||||
<AlertTriangle :size="18" class="mr-3 mt-0.5"/>
|
||||
<div class="flex-1 text-sm">
|
||||
<p>{{ t('sign.documentCreation.restrictedToAdmins') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Alert>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user