Add multi-user support via token-based routing and users.json config

Each user gets a unique secret token embedded in their webhook URL
(/webhook/:token). Credentials are loaded from users.json at startup
instead of environment variables, making it easy to add or remove users
without redeploying.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
nickandClaude Sonnet 4.6 committed 2026-06-18 17:27:08 -07:00
1 parent 902cd59d11
commit 68be646f37
4 files changed
+87 -23

No files matched your search

+1
View File
@@ -1,2 +1,3 @@
node_modules/ node_modules/
.env .env
users.json
+49 -11
View File
@@ -1,27 +1,65 @@
# AudioPen Nextcloud Webhook # AudioPen Nextcloud Webhook
A small Node.js webhook service that receives notes from AudioPen and uploads them to a folder in Nextcloud using WebDAV. A small Node.js webhook service that receives notes from AudioPen and uploads them to a folder in Nextcloud using WebDAV. Supports multiple users, each with their own Nextcloud credentials and a unique webhook token.
When AudioPen sends a webhook request, the service: When AudioPen sends a webhook request, the service:
1. Receives the note content 1. Validates the token in the URL
2. Creates a timestamped Markdown file 2. Receives the note content
3. Uploads the file to the configured Nextcloud folder 3. Creates a timestamped Markdown file
4. Uploads the file to the configured Nextcloud folder
Example filename: Example filename:
```text ```text
note-2026-05-19T22-15-10.123Z.md 2026-05-19T22-15-10.123Z-my-note-title.md
``` ```
## Example `.env` ## Setup
Create a file named `.env` in the project root directory: ### 1. Configure users
Copy `users.json.example` to `users.json` and fill in credentials for each user:
```json
{
"abc123secrettoken": {
"nextcloudUrl": "https://nextcloud.example.com",
"username": "alice",
"password": "alice-app-password",
"folder": "AudioPen"
}
}
```
Each key is a secret token that identifies a user. Generate one with:
```bash
node -e "console.log(crypto.randomUUID())"
```
### 2. Configure the server
Optionally create a `.env` file to set the port or a custom path to `users.json`:
```env ```env
NEXTCLOUD_URL=https://nextcloud.example.com
NEXTCLOUD_USERNAME=myuser
NEXTCLOUD_PASSWORD=my-app-password
NEXTCLOUD_FOLDER=AudioPen
PORT=3000 PORT=3000
USERS_FILE=users.json
``` ```
### 3. Run
```bash
npm install
node server.js
```
### 4. Configure AudioPen
In AudioPen's webhook settings, set the URL to:
```
https://your-server/webhook/<token>
```
Each user gets their own URL containing their unique token.
+23 -12
View File
@@ -1,6 +1,7 @@
import 'dotenv/config' import 'dotenv/config'
import express from 'express' import express from 'express'
import axios from 'axios' import axios from 'axios'
import { readFileSync } from 'fs'
const app = express() const app = express()
@@ -10,14 +11,24 @@ app.use(express.text({ type: '*/*' }))
const PORT = process.env.PORT || 3000 const PORT = process.env.PORT || 3000
const NEXTCLOUD_URL = process.env.NEXTCLOUD_URL const USERS_FILE = process.env.USERS_FILE || 'users.json'
const NEXTCLOUD_USERNAME = process.env.NEXTCLOUD_USERNAME let users = {}
const NEXTCLOUD_PASSWORD = process.env.NEXTCLOUD_PASSWORD
const NEXTCLOUD_FOLDER = process.env.NEXTCLOUD_FOLDER || 'AudioPen'
app.post('/webhook', async (req, res) => {
try { try {
console.log('Received webhook') users = JSON.parse(readFileSync(USERS_FILE, 'utf8'))
console.log(`Loaded ${Object.keys(users).length} user(s) from ${USERS_FILE}`)
} catch (err) {
console.error(`Failed to load ${USERS_FILE}: ${err.message}`)
process.exit(1)
}
app.post('/webhook/:token', async (req, res) => {
const user = users[req.params.token]
if (!user) {
return res.status(401).json({ error: 'Invalid token' })
}
try {
console.log(`Received webhook for user: ${user.username}`)
console.log('HEADERS:', req.headers) console.log('HEADERS:', req.headers)
console.log('RAW BODY:', req.body) console.log('RAW BODY:', req.body)
@@ -63,16 +74,16 @@ ${transcript}
` `
const webdavUrl = const webdavUrl =
`${process.env.NEXTCLOUD_URL}` + `${user.nextcloudUrl}` +
`/remote.php/dav/files/` + `/remote.php/dav/files/` +
`${process.env.NEXTCLOUD_USERNAME}/` + `${user.username}/` +
`${process.env.NEXTCLOUD_FOLDER}/` + `${user.folder}/` +
`${filename}` `${filename}`
await axios.put(webdavUrl, markdown, { await axios.put(webdavUrl, markdown, {
auth: { auth: {
username: process.env.NEXTCLOUD_USERNAME, username: user.username,
password: process.env.NEXTCLOUD_PASSWORD password: user.password
}, },
headers: { headers: {
'Content-Type': 'text/markdown' 'Content-Type': 'text/markdown'
+14
View File
@@ -0,0 +1,14 @@
{
"abc123secrettoken": {
"nextcloudUrl": "https://nextcloud.example.com",
"username": "alice",
"password": "alice-app-password",
"folder": "AudioPen"
},
"xyz789anothertoken": {
"nextcloudUrl": "https://nextcloud.example.com",
"username": "bob",
"password": "bob-app-password",
"folder": "AudioPen"
}
}