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:
- Open the invite. Your invitation email has a Set your password button
and an
obsidian://vaultguard-invitelink that opens Obsidian with your organization pre-filled. (VaultGuard never emails passwords.) - 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.
- 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.
- 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:
| 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
--usernamemust 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
OPTIONALfor non-prod stages — onlystage = "prod"flipsmfa_configurationtoON(seeterraform/modules/cognito/main.tf). Usedev.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
- Open Obsidian
- Settings > Community plugins > Turn off Restricted mode
- Find Obsidian VaultGuard and toggle it ON
- 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. Whendomain_nameis set in your tfvars, preferapi_custom_domain(e.g.https://api.example.com). The admin panel is hosted separately and should be configured withVITE_API_BASE_URLpointing 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 destroymay 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, notadminuser. - Verify the password was set as permanent:
aws cognito-idp admin-get-user --user-pool-id $USER_POOL_ID --username adminuser— status should beCONFIRMED, notFORCE_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
GenerateDataKeypermission 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:
- Set up MFA via CLI (see step 3.5 above)
- Deploy with
stage = "dev"(or"staging") — MFA is onlyONwhenstage = "prod". Seemfa_configurationinterraform/modules/cognito/main.tf.
"Failed to load users: Forbidden"
- The user must be in the
adminCognito group:aws cognito-idp admin-list-groups-for-user --user-pool-id $USER_POOL_ID --username adminuser - If using CloudFront, ensure the
/usersand/users/*behaviors are configured (they are by default interraform/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