mirror of
https://github.com/btouchard/ackify-ce.git
synced 2026-02-08 23:08:58 -06:00
feat: standardize environment variable names with ACKIFY_ prefix
- Renamed all environment variables to use consistent ACKIFY_ prefix - Updated configuration files, Docker compose, and build documentation - Modified database connection variables and OAuth configuration - Updated crypto key environment variable reference - Ensured consistency across all configuration files
This commit is contained in:
46
.env.example
46
.env.example
@@ -1,43 +1,43 @@
|
||||
# Application Configuration
|
||||
APP_NAME=ackify-ce
|
||||
APP_DNS=your-domain.com
|
||||
APP_BASE_URL=https://your-domain.com
|
||||
APP_ORGANISATION="Your Organization Name"
|
||||
|
||||
# Database Configuration
|
||||
POSTGRES_USER=ackifyr
|
||||
POSTGRES_PASSWORD=your_secure_password
|
||||
POSTGRES_DB=ackify
|
||||
DB_DSN=postgres://user:pass@db:5432/ack?sslmode=disable
|
||||
|
||||
# Application Configuration
|
||||
APP_NAME=ackify
|
||||
APP_DNS=sign.your-domain.com
|
||||
ACKIFY_BASE_URL=https://sign.your-domain.com
|
||||
ACKIFY_ORGANISATION="Your Organization Name"
|
||||
ACKIFY_DB_DSN="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@ackify-db:5432/${POSTGRES_DB}?sslmode=disable"
|
||||
|
||||
# OAuth2 Configuration - Generic Provider
|
||||
OAUTH_CLIENT_ID=your_oauth_client_id
|
||||
OAUTH_CLIENT_SECRET=your_oauth_client_secret
|
||||
OAUTH_ALLOWED_DOMAIN=your-organization.com
|
||||
ACKIFY_OAUTH_CLIENT_ID=your_oauth_client_id
|
||||
ACKIFY_OAUTH_CLIENT_SECRET=your_oauth_client_secret
|
||||
ACKIFY_OAUTH_ALLOWED_DOMAIN=your-organization.com
|
||||
|
||||
# OAuth2 Provider Configuration
|
||||
# Use OAUTH_PROVIDER to configure popular providers automatically:
|
||||
# Use ACKIFY_OAUTH_PROVIDER to configure popular providers automatically:
|
||||
# - "google" for Google OAuth2
|
||||
# - "github" for GitHub OAuth2
|
||||
# - "gitlab" for GitLab OAuth2 (set OAUTH_GITLAB_URL if self-hosted)
|
||||
# - "github" for GitHub OAuth2
|
||||
# - "gitlab" for GitLab OAuth2 (set ACKIFY_OAUTH_GITLAB_URL if self-hosted)
|
||||
# - Leave empty for custom provider (requires manual URL configuration)
|
||||
OAUTH_PROVIDER=google
|
||||
ACKIFY_OAUTH_PROVIDER=google
|
||||
|
||||
# Custom OAuth2 Provider URLs (only needed if OAUTH_PROVIDER is empty)
|
||||
# OAUTH_AUTH_URL=https://your-provider.com/oauth/authorize
|
||||
# OAUTH_TOKEN_URL=https://your-provider.com/oauth/token
|
||||
# OAUTH_USERINFO_URL=https://your-provider.com/api/user
|
||||
# OAUTH_SCOPES=openid,email
|
||||
# Custom OAuth2 Provider URLs (only needed if ACKIFY_OAUTH_PROVIDER is empty)
|
||||
# ACKIFY_OAUTH_AUTH_URL=https://your-provider.com/oauth/authorize
|
||||
# ACKIFY_OAUTH_TOKEN_URL=https://your-provider.com/oauth/token
|
||||
# ACKIFY_OAUTH_USERINFO_URL=https://your-provider.com/api/user
|
||||
# ACKIFY_OAUTH_SCOPES=openid,email
|
||||
|
||||
# GitLab specific (if using gitlab as provider and self-hosted)
|
||||
# OAUTH_GITLAB_URL=https://gitlab.your-company.com
|
||||
# ACKIFY_OAUTH_GITLAB_URL=https://gitlab.your-company.com
|
||||
|
||||
# Security Configuration
|
||||
OAUTH_COOKIE_SECRET=your_base64_encoded_secret_key
|
||||
ED25519_PRIVATE_KEY_B64=your_base64_encoded_ed25519_private_key
|
||||
ACKIFY_OAUTH_COOKIE_SECRET=your_base64_encoded_secret_key
|
||||
ACKIFY_ED25519_PRIVATE_KEY=your_base64_encoded_ed25519_private_key
|
||||
|
||||
# Server Configuration
|
||||
LISTEN_ADDR=:8080
|
||||
ACKIFY_LISTEN_ADDR=:8080
|
||||
|
||||
# Template Configuration
|
||||
# ACKIFY_TEMPLATES_DIR=/custom/path/to/templates
|
||||
16
BUILD.md
16
BUILD.md
@@ -51,14 +51,18 @@ cp .env.example .env
|
||||
|
||||
Required environment variables:
|
||||
|
||||
- `APP_BASE_URL`: Public URL of your application
|
||||
- `OAUTH_CLIENT_ID`: OAuth2 client ID
|
||||
- `OAUTH_CLIENT_SECRET`: OAuth2 client secret
|
||||
- `DB_DSN`: PostgreSQL connection string
|
||||
- `OAUTH_COOKIE_SECRET`: Base64-encoded secret for session cookies
|
||||
- `ACKIFY_BASE_URL`: Public URL of your application
|
||||
- `ACKIFY_OAUTH_CLIENT_ID`: OAuth2 client ID
|
||||
- `ACKIFY_OAUTH_CLIENT_SECRET`: OAuth2 client secret
|
||||
- `ACKIFY_DB_DSN`: PostgreSQL connection string
|
||||
- `ACKIFY_OAUTH_COOKIE_SECRET`: Base64-encoded secret for session cookies
|
||||
|
||||
Optional template configuration:
|
||||
Optional configuration:
|
||||
- `ACKIFY_TEMPLATES_DIR`: Custom path to HTML templates directory (defaults to relative path for development, `/app/templates` in Docker)
|
||||
- `ACKIFY_LISTEN_ADDR`: Server listen address (default: `:8080`)
|
||||
- `ACKIFY_ED25519_PRIVATE_KEY`: Base64-encoded Ed25519 private key for signatures
|
||||
- `ACKIFY_OAUTH_PROVIDER`: OAuth provider (`google`, `github`, `gitlab` or empty for custom)
|
||||
- `ACKIFY_OAUTH_ALLOWED_DOMAIN`: Domain restriction for OAuth users
|
||||
|
||||
### OAuth2 Providers
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
var dbDSN = flag.String("db-dsn", os.Getenv("DB_DSN"), "Database DSN")
|
||||
var dbDSN = flag.String("db-dsn", os.Getenv("ACKIFY_DB_DSN"), "Database DSN")
|
||||
var migrationsPath = flag.String("migrations-path", "file://migrations", "Path to migrations directory")
|
||||
flag.Parse()
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@ services:
|
||||
image: btouchard/ackify-ce
|
||||
container_name: ackify-ce-migrate
|
||||
environment:
|
||||
APP_BASE_URL: "${APP_BASE_URL}"
|
||||
APP_ORGANISATION: "${APP_ORGANISATION}"
|
||||
OAUTH_PROVIDER: "${OAUTH_PROVIDER}"
|
||||
OAUTH_CLIENT_ID: "${OAUTH_CLIENT_ID}"
|
||||
OAUTH_CLIENT_SECRET: "${OAUTH_CLIENT_SECRET}"
|
||||
DB_DSN: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@ackify-db:5432/${POSTGRES_DB}?sslmode=disable"
|
||||
ACKIFY_BASE_URL: "${ACKIFY_BASE_URL}"
|
||||
ACKIFY_ORGANISATION: "${ACKIFY_ORGANISATION}"
|
||||
ACKIFY_OAUTH_PROVIDER: "${ACKIFY_OAUTH_PROVIDER}"
|
||||
ACKIFY_OAUTH_CLIENT_ID: "${ACKIFY_OAUTH_CLIENT_ID}"
|
||||
ACKIFY_OAUTH_CLIENT_SECRET: "${ACKIFY_OAUTH_CLIENT_SECRET}"
|
||||
ACKIFY_DB_DSN: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@ackify-db:5432/${POSTGRES_DB}?sslmode=disable"
|
||||
depends_on:
|
||||
ackify-db:
|
||||
condition: service_healthy
|
||||
@@ -25,16 +25,16 @@ services:
|
||||
container_name: ackify-ce
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
APP_BASE_URL: "https://${APP_DNS}"
|
||||
APP_ORGANISATION: "${APP_ORGANISATION}"
|
||||
OAUTH_PROVIDER: "${OAUTH_PROVIDER}"
|
||||
OAUTH_CLIENT_ID: "${OAUTH_CLIENT_ID}"
|
||||
OAUTH_CLIENT_SECRET: "${OAUTH_CLIENT_SECRET}"
|
||||
OAUTH_ALLOWED_DOMAIN: "${OAUTH_ALLOWED_DOMAIN}"
|
||||
OAUTH_COOKIE_SECRET: "${OAUTH_COOKIE_SECRET}"
|
||||
DB_DSN: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@ackify-db:5432/${POSTGRES_DB}?sslmode=disable"
|
||||
ED25519_PRIVATE_KEY_B64: "${ED25519_PRIVATE_KEY_B64}"
|
||||
LISTEN_ADDR: ":8080"
|
||||
ACKIFY_BASE_URL: "https://${APP_DNS}"
|
||||
ACKIFY_ORGANISATION: "${ACKIFY_ORGANISATION}"
|
||||
ACKIFY_OAUTH_PROVIDER: "${ACKIFY_OAUTH_PROVIDER}"
|
||||
ACKIFY_OAUTH_CLIENT_ID: "${ACKIFY_OAUTH_CLIENT_ID}"
|
||||
ACKIFY_OAUTH_CLIENT_SECRET: "${ACKIFY_OAUTH_CLIENT_SECRET}"
|
||||
ACKIFY_OAUTH_ALLOWED_DOMAIN: "${ACKIFY_OAUTH_ALLOWED_DOMAIN}"
|
||||
ACKIFY_OAUTH_COOKIE_SECRET: "${ACKIFY_OAUTH_COOKIE_SECRET}"
|
||||
ACKIFY_DB_DSN: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@ackify-db:5432/${POSTGRES_DB}?sslmode=disable"
|
||||
ACKIFY_ED25519_PRIVATE_KEY: "${ACKIFY_ED25519_PRIVATE_KEY}"
|
||||
ACKIFY_LISTEN_ADDR: ":8080"
|
||||
depends_on:
|
||||
ackify-migrate:
|
||||
condition: service_completed_successfully
|
||||
|
||||
@@ -51,21 +51,21 @@ func Load() (*Config, error) {
|
||||
config := &Config{}
|
||||
|
||||
// App config
|
||||
baseURL := mustGetEnv("APP_BASE_URL")
|
||||
baseURL := mustGetEnv("ACKIFY_BASE_URL")
|
||||
config.App.BaseURL = baseURL
|
||||
config.App.Organisation = mustGetEnv("APP_ORGANISATION")
|
||||
config.App.Organisation = mustGetEnv("ACKIFY_ORGANISATION")
|
||||
config.App.SecureCookies = strings.HasPrefix(strings.ToLower(baseURL), "https://")
|
||||
|
||||
// Database config
|
||||
config.Database.DSN = mustGetEnv("DB_DSN")
|
||||
config.Database.DSN = mustGetEnv("ACKIFY_DB_DSN")
|
||||
|
||||
// OAuth config
|
||||
config.OAuth.ClientID = mustGetEnv("OAUTH_CLIENT_ID")
|
||||
config.OAuth.ClientSecret = mustGetEnv("OAUTH_CLIENT_SECRET")
|
||||
config.OAuth.AllowedDomain = os.Getenv("OAUTH_ALLOWED_DOMAIN")
|
||||
config.OAuth.ClientID = mustGetEnv("ACKIFY_OAUTH_CLIENT_ID")
|
||||
config.OAuth.ClientSecret = mustGetEnv("ACKIFY_OAUTH_CLIENT_SECRET")
|
||||
config.OAuth.AllowedDomain = os.Getenv("ACKIFY_OAUTH_ALLOWED_DOMAIN")
|
||||
|
||||
// Configure OAuth endpoints based on provider or use custom URLs
|
||||
provider := strings.ToLower(getEnv("OAUTH_PROVIDER", ""))
|
||||
provider := strings.ToLower(getEnv("ACKIFY_OAUTH_PROVIDER", ""))
|
||||
switch provider {
|
||||
case "google":
|
||||
config.OAuth.AuthURL = "https://accounts.google.com/o/oauth2/auth"
|
||||
@@ -78,17 +78,17 @@ func Load() (*Config, error) {
|
||||
config.OAuth.UserInfoURL = "https://api.github.com/user"
|
||||
config.OAuth.Scopes = []string{"user:email", "read:user"}
|
||||
case "gitlab":
|
||||
gitlabURL := getEnv("OAUTH_GITLAB_URL", "https://gitlab.com")
|
||||
gitlabURL := getEnv("ACKIFY_OAUTH_GITLAB_URL", "https://gitlab.com")
|
||||
config.OAuth.AuthURL = fmt.Sprintf("%s/oauth/authorize", gitlabURL)
|
||||
config.OAuth.TokenURL = fmt.Sprintf("%s/oauth/token", gitlabURL)
|
||||
config.OAuth.UserInfoURL = fmt.Sprintf("%s/api/v4/user", gitlabURL)
|
||||
config.OAuth.Scopes = []string{"read_user", "profile"}
|
||||
default:
|
||||
// Custom OAuth provider - all URLs must be explicitly set
|
||||
config.OAuth.AuthURL = mustGetEnv("OAUTH_AUTH_URL")
|
||||
config.OAuth.TokenURL = mustGetEnv("OAUTH_TOKEN_URL")
|
||||
config.OAuth.UserInfoURL = mustGetEnv("OAUTH_USERINFO_URL")
|
||||
scopesStr := getEnv("OAUTH_SCOPES", "openid,email,profile")
|
||||
config.OAuth.AuthURL = mustGetEnv("ACKIFY_OAUTH_AUTH_URL")
|
||||
config.OAuth.TokenURL = mustGetEnv("ACKIFY_OAUTH_TOKEN_URL")
|
||||
config.OAuth.UserInfoURL = mustGetEnv("ACKIFY_OAUTH_USERINFO_URL")
|
||||
scopesStr := getEnv("ACKIFY_OAUTH_SCOPES", "openid,email,profile")
|
||||
config.OAuth.Scopes = strings.Split(scopesStr, ",")
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ func Load() (*Config, error) {
|
||||
config.OAuth.CookieSecret = cookieSecret
|
||||
|
||||
// Server config
|
||||
config.Server.ListenAddr = getEnv("LISTEN_ADDR", ":8080")
|
||||
config.Server.ListenAddr = getEnv("ACKIFY_LISTEN_ADDR", ":8080")
|
||||
|
||||
return config, nil
|
||||
}
|
||||
@@ -124,11 +124,11 @@ func getEnv(key, defaultValue string) string {
|
||||
|
||||
// parseCookieSecret parses the cookie secret from environment
|
||||
func parseCookieSecret() ([]byte, error) {
|
||||
raw := os.Getenv("OAUTH_COOKIE_SECRET")
|
||||
raw := os.Getenv("ACKIFY_OAUTH_COOKIE_SECRET")
|
||||
if raw == "" {
|
||||
// Generate random 32 bytes for development
|
||||
secret := securecookie.GenerateRandomKey(32)
|
||||
fmt.Println("[WARN] OAUTH_COOKIE_SECRET not set, generated volatile secret (sessions reset on restart)")
|
||||
fmt.Println("[WARN] ACKIFY_OAUTH_COOKIE_SECRET not set, generated volatile secret (sessions reset on restart)")
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ func SetupTestDB(t *testing.T) *TestDB {
|
||||
t.Skip("Skipping integrations test (INTEGRATION_TESTS not set)")
|
||||
}
|
||||
|
||||
dsn := os.Getenv("DB_DSN")
|
||||
dsn := os.Getenv("ACKIFY_DB_DSN")
|
||||
if dsn == "" {
|
||||
dsn = "postgres://postgres:testpassword@localhost:5432/ackify_test?sslmode=disable"
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func canonicalPayload(docID string, user *models.User, timestamp time.Time, nonc
|
||||
|
||||
// loadOrGenerateKeys loads existing keys or generates new ones
|
||||
func loadOrGenerateKeys() (ed25519.PrivateKey, ed25519.PublicKey, error) {
|
||||
b64Key := strings.TrimSpace(os.Getenv("ED25519_PRIVATE_KEY_B64"))
|
||||
b64Key := strings.TrimSpace(os.Getenv("ACKIFY_ED25519_PRIVATE_KEY"))
|
||||
|
||||
if b64Key != "" {
|
||||
keyBytes, err := base64.StdEncoding.DecodeString(b64Key)
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
func TestEd25519Signer_NewEd25519Signer(t *testing.T) {
|
||||
t.Run("creates new signer successfully", func(t *testing.T) {
|
||||
// Clear environment variable to force generation
|
||||
originalKey := os.Getenv("ED25519_PRIVATE_KEY_B64")
|
||||
originalKey := os.Getenv("ACKIFY_ED25519_PRIVATE_KEY")
|
||||
os.Unsetenv("ED25519_PRIVATE_KEY_B64")
|
||||
defer func() {
|
||||
if originalKey != "" {
|
||||
@@ -398,7 +398,7 @@ func TestEd25519Signer_GetPublicKey(t *testing.T) {
|
||||
|
||||
t.Run("different signers have different public keys", func(t *testing.T) {
|
||||
// Clear environment to force generation of different keys
|
||||
originalKey := os.Getenv("ED25519_PRIVATE_KEY_B64")
|
||||
originalKey := os.Getenv("ACKIFY_ED25519_PRIVATE_KEY")
|
||||
os.Unsetenv("ED25519_PRIVATE_KEY_B64")
|
||||
defer func() {
|
||||
if originalKey != "" {
|
||||
|
||||
@@ -19,14 +19,14 @@ import (
|
||||
"github.com/btouchard/ackify-ce/pkg/crypto"
|
||||
)
|
||||
|
||||
// Server represents the Ackify CE web server
|
||||
// Server represents the Ackify web server
|
||||
type Server struct {
|
||||
httpServer *http.Server
|
||||
db *sql.DB
|
||||
router *chi.Mux
|
||||
}
|
||||
|
||||
// NewServer creates a new Ackify CE server instance
|
||||
// NewServer creates a new Ackify server instance
|
||||
func NewServer(ctx context.Context) (*Server, error) {
|
||||
// Initialize infrastructure
|
||||
cfg, db, tmpl, signer, err := initInfrastructure(ctx)
|
||||
@@ -212,8 +212,8 @@ func getTemplatesDir() string {
|
||||
|
||||
// Fallback for development: check multiple possible paths
|
||||
possiblePaths := []string{
|
||||
"templates", // When running from project root
|
||||
"./templates", // Alternative relative path
|
||||
"templates", // When running from project root
|
||||
"./templates", // Alternative relative path
|
||||
}
|
||||
|
||||
for _, path := range possiblePaths {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-white">Ackify CE</h2>
|
||||
<h2 class="text-2xl font-bold text-white">Ackify</h2>
|
||||
<p class="text-primary-100">La solution professionnelle pour valider la lecture de vos documents</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-success-700 mb-2">Document Déjà Signé</h3>
|
||||
<h3 class="text-xl font-bold text-success-700 mb-2">Document signé</h3>
|
||||
<p class="text-slate-600 mb-4">Vous avez confirmé la lecture de ce document</p>
|
||||
|
||||
<div class="bg-success-50 border border-success-200 rounded-2xl p-6">
|
||||
@@ -58,8 +58,8 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-warning-700 mb-2">Document Non Signé</h3>
|
||||
<p class="text-slate-600 mb-6">Vous devez confirmer avoir lu et approuvé ce document</p>
|
||||
<h3 class="text-xl font-bold text-warning-700 mb-2">Document à signer</h3>
|
||||
<p class="text-slate-600 mb-6">Vous devez confirmer avoir lu et compris ce document</p>
|
||||
|
||||
<div class="bg-warning-50 border border-warning-200 rounded-2xl p-6 mb-6">
|
||||
<div class="flex items-start space-x-3">
|
||||
@@ -82,7 +82,7 @@
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/>
|
||||
</svg>
|
||||
<span>J'ai lu et j'approuve ce document</span>
|
||||
<span>Je certifie avoir lu et compris ce document</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -95,11 +95,11 @@
|
||||
<div class="bg-white rounded-2xl border border-slate-200 p-6">
|
||||
<h4 class="font-semibold text-slate-900 mb-4">Actions supplémentaires</h4>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<a href="/embed?doc={{.DocID}}" target="_blank" class="flex items-center justify-center space-x-2 px-4 py-3 bg-slate-100 hover:bg-slate-200 text-slate-700 rounded-xl transition-colors text-sm font-medium text-center">
|
||||
<a href="/signatures" target="_blank" class="flex items-center justify-center space-x-2 px-4 py-3 bg-slate-100 hover:bg-slate-200 text-slate-700 rounded-xl transition-colors text-sm font-medium text-center">
|
||||
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
<span>Widget embarqué</span>
|
||||
<span>Voir mes signatures</span>
|
||||
</a>
|
||||
|
||||
<a href="/" class="flex items-center justify-center space-x-2 px-4 py-3 bg-primary-100 hover:bg-primary-200 text-primary-700 rounded-xl transition-colors text-sm font-medium text-center">
|
||||
|
||||
Reference in New Issue
Block a user