← All docs

Obsidian VaultGuard — Setup Guide

Two deployment modes: Local Dev Server for development/testing without AWS, and AWS (Terraform) for production.

First login flow. After authenticating, every Obsidian local vault must be bound to a server-side Vault entity. The plugin opens a picker the first time you log in — pick an existing vault you're a member of, or (admins) create a new one. Without binding, file/permission API calls are refused. See VAULTS.md for the full data model.


Signing in for the first time (end users)

If you were invited to a VaultGuard organization, you don't need any of the deployment steps below — your admin already runs the server. Here's the whole first-run:

  1. Open the invite. Your invitation email has a Set your password button and an obsidian://vaultguard-invite link that opens Obsidian with your organization pre-filled. (VaultGuard never emails passwords.)
  2. Set your password. The plugin sends a verification code to your email; enter it and choose a password — at least 12 characters with an uppercase letter, a lowercase letter, a number, and a symbol. A live checklist shows each requirement as you meet it, and the eye icon reveals what you typed.
  3. Two-factor (if your org requires it). Scan the QR code with an authenticator app, enter the 6-digit code, and save the recovery codes — they're the only way back in if you lose your device.
  4. Pick your vault. The plugin links this Obsidian folder to a server vault. If you belong to exactly one, it binds automatically; otherwise pick the right one.

That's it — your notes now sync end-to-end encrypted, and the file header shows who can access each note. Hitting a snag? See Signing in & first-run troubleshooting.


Option A: Local Dev Server (No AWS Required)

The fastest way to test the plugin. Uses an in-memory Express mock server.

Start the dev server

cd dev-server
npm install
npm run dev

The server starts on http://localhost:3333 and prints pre-seeded test accounts:

Email Password Role
admin@test.com admin123 admin
editor@test.com editor123 editor
viewer@test.com viewer123 viewer

Configure the plugin

In Obsidian, go to Settings > Obsidian VaultGuard > Connection:

Setting Value
API endpoint http://localhost:3333
Organization ID org-dev-001
Cognito User Pool ID local-dev
Cognito Client ID local-dev

Click the shield icon and log in with any test account. No MFA, no AWS credentials needed.

Note: The dev server uses fake JWT tokens and in-memory storage. Data resets on restart. It does not use Cognito at all.


Option B: AWS Deployment (Terraform)

Full production deployment with Cognito authentication, KMS encryption, DynamoDB, S3, and CloudFront.

Prerequisites

Tool Version Install
Terraform >= 1.5.0 brew install terraform
AWS CLI >= 2.x brew install awscli
Node.js >= 18.x brew install node
Obsidian >= 1.4.0 obsidian.md
# Verify
terraform version    # >= 1.5.0
aws --version        # >= 2.x
node --version       # >= 18

AWS credentials

aws configure
# or export AWS_PROFILE=your-profile

You need permissions for: IAM, KMS, S3, DynamoDB, Cognito, Lambda, API Gateway, CloudFront, CloudWatch, SNS.


1. Build the Lambda Functions

cd infrastructure
npm install
node build-lambdas.mjs

This creates compiled bundles in infrastructure/dist/ for all Lambda functions (auth, files, permissions, audit, users, billing). The Terraform lambda module zips and uploads these bundles, so this step must run before every deploy that touches Lambda code.


2. Deploy the Stack

2.1 Initialize Terraform (first time only)

cd terraform
terraform init

This downloads the AWS and archive providers and prepares local backend state. To use S3-backed state for a shared team, uncomment the backend "s3" block in versions.tf before running init.

2.2 Review the plan

terraform plan -var-file=environments/dev.tfvars

The environments/ folder ships with dev.tfvars and prod.tfvars you can edit (region, admin email, key/session lifetimes, optional domain_name). Override anything ad-hoc with -var 'key=value'.

2.3 Apply

terraform apply -var-file=environments/dev.tfvars

Type yes when prompted. The first deploy takes 5–10 minutes (Cognito + CloudFront are the slow steps).

2.4 Save the outputs

After apply, Terraform prints outputs like:

api_url             = "https://xxxxxxxxxx.execute-api.eu-central-1.amazonaws.com/dev"
cloudfront_url      = "https://d1234567890.cloudfront.net"
user_pool_id        = "eu-central-1_AbCdEfGhI"
user_pool_client_id = "1a2b3c4d5e6f7g8h9i0j"
api_custom_domain   = "https://api.example.com"   # only when domain_name is set

Re-print at any time with terraform output. Pull a single value with terraform output -raw user_pool_id. Save these — you need them for the plugin configuration.


3. Create Users in Cognito

Important: The Cognito user pool is configured with email as a sign-in alias. When creating users, the --username must NOT be in email format. Use a plain username instead — the email is set as an attribute.

3.1 Create the admin group

USER_POOL_ID="$(terraform output -raw user_pool_id)"

aws cognito-idp create-group \
  --user-pool-id $USER_POOL_ID \
  --group-name admin \
  --description "Full admin access"

3.2 Create an admin user

aws cognito-idp admin-create-user \
  --user-pool-id $USER_POOL_ID \
  --username adminuser \
  --user-attributes \
    Name=email,Value=admin@yourcompany.com \
    Name=email_verified,Value=true \
    Name=name,Value="Admin User" \
  --temporary-password 'TempGuard!an123' \
  --message-action SUPPRESS

3.3 Set a permanent password

Skip the forced password change on first login:

aws cognito-idp admin-set-user-password \
  --user-pool-id $USER_POOL_ID \
  --username adminuser \
  --password 'YourSecure!Pass456' \
  --permanent

Password requirements: minimum 12 characters, uppercase, lowercase, numbers, and symbols.

3.4 Add user to admin group

aws cognito-idp admin-add-user-to-group \
  --user-pool-id $USER_POOL_ID \
  --username adminuser \
  --group-name admin

3.5 Set up MFA (required)

The user pool requires MFA. You must associate a TOTP device before the plugin can log in:

# Start MFA setup
SECRET=$(aws cognito-idp admin-set-user-mfa-preference \
  --user-pool-id $USER_POOL_ID \
  --username adminuser \
  --software-token-mfa-settings Enabled=true,PreferredMfa=true 2>&1)

# Get TOTP secret for authenticator app
CLIENT_ID="$(terraform output -raw user_pool_client_id)"
REGION="eu-central-1"  # adjust to your region

# Initiate auth to get the session
AUTH_RESULT=$(aws cognito-idp initiate-auth \
  --client-id $CLIENT_ID \
  --auth-flow USER_PASSWORD_AUTH \
  --auth-parameters USERNAME=admin@yourcompany.com,PASSWORD='YourSecure!Pass456' \
  --region $REGION)

SESSION=$(echo $AUTH_RESULT | python3 -c "import sys,json; print(json.load(sys.stdin).get('Session',''))")

# If you get MFA_SETUP challenge, associate TOTP:
TOTP_RESULT=$(aws cognito-idp associate-software-token \
  --session "$SESSION" \
  --region $REGION)

TOTP_SECRET=$(echo $TOTP_RESULT | python3 -c "import sys,json; print(json.load(sys.stdin)['SecretCode'])")
SESSION2=$(echo $TOTP_RESULT | python3 -c "import sys,json; print(json.load(sys.stdin).get('Session',''))")

echo "Add this secret to your authenticator app: $TOTP_SECRET"
echo "Then enter the 6-digit code below."

Add the secret to your authenticator app (Google Authenticator, Authy, 1Password, etc.), then verify:

read -p "Enter TOTP code: " TOTP_CODE

aws cognito-idp verify-user-attribute \
  --access-token "..." \
  --attribute-name phone_number \
  --code "$TOTP_CODE" 2>/dev/null

# Verify the TOTP setup
aws cognito-idp respond-to-auth-challenge \
  --client-id $CLIENT_ID \
  --challenge-name MFA_SETUP \
  --session "$SESSION2" \
  --challenge-responses USERNAME=admin@yourcompany.com,SOFTWARE_TOKEN_MFA_CODE=$TOTP_CODE \
  --region $REGION

Tip: MFA is already OPTIONAL for non-prod stages — only stage = "prod" flips mfa_configuration to ON (see terraform/modules/cognito/main.tf). Use dev.tfvars (the default) to skip TOTP enrollment during testing.

3.6 (Optional) Create additional users

# Create viewer user
aws cognito-idp admin-create-user \
  --user-pool-id $USER_POOL_ID \
  --username vieweruser \
  --user-attributes \
    Name=email,Value=viewer@yourcompany.com \
    Name=email_verified,Value=true \
    Name=name,Value="Viewer User" \
  --temporary-password 'TempView!er123' \
  --message-action SUPPRESS

aws cognito-idp admin-set-user-password \
  --user-pool-id $USER_POOL_ID \
  --username vieweruser \
  --password 'ViewerSecure!Pass789' \
  --permanent

4. Build the Plugin

cd ..  # back to project root
npm install
npm run build

This produces main.js, manifest.json, and styles.css.


5. Install in Obsidian

Option A: Symlink (recommended for development)

VAULT_PATH="$HOME/Documents/MyVault"  # adjust to your vault

mkdir -p "$VAULT_PATH/.obsidian/plugins/vaultguard"
ln -sf "$(pwd)/main.js" "$VAULT_PATH/.obsidian/plugins/vaultguard/main.js"
ln -sf "$(pwd)/manifest.json" "$VAULT_PATH/.obsidian/plugins/vaultguard/manifest.json"
ln -sf "$(pwd)/styles.css" "$VAULT_PATH/.obsidian/plugins/vaultguard/styles.css"

Option B: Copy files

VAULT_PATH="$HOME/Documents/MyVault"
PLUGIN_DIR="$VAULT_PATH/.obsidian/plugins/vaultguard"

mkdir -p "$PLUGIN_DIR"
cp main.js manifest.json styles.css "$PLUGIN_DIR/"

Enable the plugin

  1. Open Obsidian
  2. Settings > Community plugins > Turn off Restricted mode
  3. Find Obsidian VaultGuard and toggle it ON
  4. A shield icon appears in the left ribbon

6. Configure the Plugin

Open Settings > Obsidian VaultGuard:

Setting Value Source
API endpoint https://d1234567890.cloudfront.net terraform output -raw cloudfront_url
Organization ID vaultguard-dev Any name you choose
Cognito User Pool ID eu-central-1_AbCdEfGhI terraform output -raw user_pool_id
Cognito Client ID 1a2b3c4d5e6f7g8h9i0j terraform output -raw user_pool_client_id

Which endpoint URL to use? You can use either the API Gateway URL (api_url) or the CloudFront URL (cloudfront_url) for the plugin API base. The CloudFront distribution fronts the API only in this architecture. When domain_name is set in your tfvars, prefer api_custom_domain (e.g. https://api.example.com). The admin panel is hosted separately and should be configured with VITE_API_BASE_URL pointing at the API URL.


7. Login and Test

Click the shield icon or use command palette > VaultGuard: Login.

Enter:

  • Email: admin@yourcompany.com (the email you set as an attribute)
  • Password: The permanent password you set
  • MFA Code: 6-digit code from your authenticator app (if MFA is required)

On success: VaultGuard: Logged in as Admin User

The Account section in plugin settings shows who you're logged in as and provides a logout button.


8. Deploy the Admin Panel (Optional)

The admin panel is a separate React web app. It is not served from the API CloudFront distribution in the current Terraform setup.

cd admin-panel
npm install

# Build for production (uses .env.production)
npm run build

# Deploy the built assets to your static hosting target
# Examples: AWS Amplify, Netlify, Vercel, S3+CloudFront, etc.

Set VITE_API_BASE_URL so the admin panel talks to the VaultGuard API, then publish admin-panel/dist on your preferred static host. The host URL for that static site becomes the admin panel URL.


9. Development Workflow

Watch mode

npm run dev  # auto-rebuild plugin on file changes

Reload Obsidian (Cmd+R) to pick up changes.

Rebuild and redeploy Lambdas

cd infrastructure
node build-lambdas.mjs
cd ../terraform
terraform apply -var-file=environments/dev.tfvars

The archive_file data sources in terraform/modules/lambda/main.tf re-hash infrastructure/dist/* on every run, so the new bundles are picked up automatically — no manual -replace needed.

View Lambda logs

# Recent logs
aws logs filter-log-events \
  --log-group-name /aws/lambda/vaultguard-auth-dev \
  --start-time $(( $(date +%s) * 1000 - 300000 )) \
  --limit 20

# Stream live
aws logs tail /aws/lambda/vaultguard-auth-dev --follow

10. Tear Down

cd terraform
terraform destroy -var-file=environments/dev.tfvars

Watch out for retained resources. S3 vault buckets and KMS keys have deletion-protection / pending-window settings — terraform destroy may leave them behind for the configured grace period. Empty the buckets first if you want an immediate teardown.


Troubleshooting

"USER_PASSWORD_AUTH flow not enabled for this client"

The Cognito app client needs ALLOW_USER_PASSWORD_AUTH enabled. This is configured in terraform/modules/cognito/main.tf under explicit_auth_flows. Redeploy after changing:

explicit_auth_flows = [
  "ALLOW_USER_PASSWORD_AUTH",  # Required
  "ALLOW_REFRESH_TOKEN_AUTH",
  "ALLOW_USER_SRP_AUTH",
]

"Incorrect username or password"

  • Cognito sign-in uses the email (not the username). Enter admin@yourcompany.com, not adminuser.
  • Verify the password was set as permanent: aws cognito-idp admin-get-user --user-pool-id $USER_POOL_ID --username adminuser — status should be CONFIRMED, not FORCE_CHANGE_PASSWORD.

Error 500 after MFA

  • Check Lambda logs: aws logs tail /aws/lambda/vaultguard-auth-dev --follow
  • Common cause: DynamoDB table schema mismatch. Redeploy the stack to recreate tables.
  • Common cause: KMS GenerateDataKey permission missing from the auth Lambda role.

"MFA_SETUP" challenge / MFA not configured

New users with MFA required must set up their authenticator before the plugin can log in. Either:

  1. Set up MFA via CLI (see step 3.5 above)
  2. Deploy with stage = "dev" (or "staging") — MFA is only ON when stage = "prod". See mfa_configuration in terraform/modules/cognito/main.tf.

"Failed to load users: Forbidden"

  • The user must be in the admin Cognito group: aws cognito-idp admin-list-groups-for-user --user-pool-id $USER_POOL_ID --username adminuser
  • If using CloudFront, ensure the /users and /users/* behaviors are configured (they are by default in terraform/modules/cloudfront/).

"Network unavailable" / ERR_NAME_NOT_RESOLVED

  • Verify the API endpoint URL in plugin settings is correct and reachable
  • If using the dev server, make sure it's running (cd dev-server && npm run dev)
  • Check that your network/firewall allows HTTPS to the AWS endpoints

No shield icon in sidebar

  • Ensure the plugin is enabled in Settings > Community plugins
  • Reload Obsidian: Cmd+R
  • Check dev console (Cmd+Option+I) for plugin load errors