couchdb-nano
couchdb-nano copied to clipboard
Nano does not work when using special characters in CouchDB password
Nano does not parse a CouchDB host, username, and password correctly if the string which is passed to the constructor contains special characters.
Steps to Reproduce (for bugs)
import * as Nano from 'nano'
let n = Nano('http://admin:g[?6asdwrF@localhost:5984');
console.log(n);
let db = n.db.use('people');
Your Environment
- Version used: 8.1.0
- Operating System and version (desktop or mobile): macOs 10.14.6
You're a lifesaver. Thank you for pointing this out!
Hi,
Perhaps the user name and password could be passed as Header in a HTTP POST (GET to?) command, something like:
$.ajax({ type: 'POST', url: http://theappurl.com/api/v1/method/, data: {}, crossDomain: true, beforeSend: function(xhr) { xhr.setRequestHeader('Authorization', 'Basic ' + btoa(unescape(encodeURIComponent(YOUR_USERNAME + ':' + YOUR_PASSWORD)))) }});
Source: https://stackoverflow.com/questions/18264601/how-to-send-a-correct-authorization-header-for-basic-authentication
Source: https://developer.mozilla.org/en-US/docs/Glossary/Base64
Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
On Wed, 7 Aug 2019 at 11:42, Matthias [email protected] wrote:
Nano does not parse a CouchDB host, username, and password correctly if the password which is passed to the constructor contains special characters. Steps to Reproduce (for bugs)
import * as Nano from 'nano'
let n = Nano('http://admin:g[?6asdwrF@localhost:5984'); console.log(n); let db = n.db.use('people');
Your Environment
- Version used: 8.1.0
- Operating System and version (desktop or mobile): macOs 10.14.6
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/apache/couchdb-nano/issues/174?email_source=notifications&email_token=ABKTZUWO5EK4TV77NTVUW3TQDKKJDA5CNFSM4IJ6N732YY3PNVWWK3TUL52HS4DFUVEXG43VMWVGG33NNVSW45C7NFSM4HD26LTA, or mute the thread https://github.com/notifications/unsubscribe-auth/ABKTZUS5I3SEBU5QANEC5MDQDKKJDANCNFSM4IJ6N73Q .
Have you tried using encodeURI
on the password?
let n = Nano('http://admin:' + encodeURI('g[?6asdwrF') + '@localhost:5984');
I suspect the problem is that the URL you used is not valid. From REPL on node.js 14:
> new URL('http://admin:g[?6asdwrF@localhost:5984')
Uncaught:
TypeError [ERR_INVALID_URL]: Invalid URL: http://admin:g[?6asdwrF@localhost:5984
at onParseError (internal/url.js:257:9)
at new URL (internal/url.js:333:5)
at repl:1:1
at Script.runInThisContext (vm.js:131:20)
at REPLServer.defaultEval (repl.js:436:29)
at bound (domain.js:429:14)
at REPLServer.runBound [as eval] (domain.js:442:12)
at REPLServer.onLine (repl.js:763:10)
at REPLServer.emit (events.js:327:22)
at REPLServer.EventEmitter.emit (domain.js:485:12) {
input: 'http://admin:g[?6asdwrF@localhost:5984',
code: 'ERR_INVALID_URL'
}
For NodeJS developer trying to use the _changes
feed, this is how I did it (you can also use request
instead of get
by simply replacing get
with request
like so: http.request({ ... })
):
http.get({
host: '127.0.0.1',
port: 5984,
path: '/books/_changes?feed=continuous',
auth: 'user:p@ssword#'
}, resp => {
resp.on('data', chunk => {
console.log('' + chunk);
});
}).on('error', err => console.log(err));
I've solved using
const opts = {
url: `http://couchdb:5984`,
requestDefaults: {
headers: {
Authorization:"Basic " + new Buffer("admin:"+cdbpass).toString('base64')
}
}
}
const couchdb = nano(opts)
What also works with nano>=9
is using cookie auth:
couchServer = nano({
url: config.dbServer.protocol + config.dbServer.host,
parseUrl: false,
requestDefaults: {
jar: true
}
});
await couchServer.auth(config.dbServer.user, config.dbServer.password);
Note: Adding the Auth header manually like this is not a good idea in nano 9:
requestDefaults: {
headers: {
Authorization:"Basic " + new Buffer("admin:"+cdbpass).toString('base64')
}
}
Because this will cause nano to log passwords on errors, see this issue. This commit shows how passwords are redacted.
So if you don't want to use cookie auth (which requires to await nano.auth(..)
), this is a good way to go with special characters inside the password:
const opts = {
url: `http://couchdb:5984`,
requestDefaults: {
headers: {
auth: {
username: "admin",
password: "/Pa$$|w0rd^",
},
}
}
}
const couchdb = nano(opts)
Hm, for me, only the approach suggested by sl45sms worked:
const nano = nanoClient({
url: `http://127.0.0.1:5984`,
requestDefaults: {
headers: {
// works, but insecure: https://github.com/apache/couchdb-nano/issues/174#issuecomment-1021215664
Authorization: "Basic " + new Buffer(`${config.storage.couchdb.user}:${config.storage.couchdb.password}`).toString('base64'),
},
},
})
nano.use('test_polling_storage').list().then(console.log)
while these 2 give Error: You are not authorized to access this db.
:
const nano = nanoClient({
url: `http://127.0.0.1:5984`,
requestDefaults: {
headers: {
auth: {
username: config.storage.couchdb.user,
password: config.storage.couchdb.password,
},
},
},
})
nano.use('test_polling_storage').list().then(console.log)
and
const nano = nanoClient({
url: `http://127.0.0.1:5984`,
requestDefaults: {
jar: true,
},
})
nano.auth(config.storage.couchdb.user, config.storage.couchdb.password)
.then(() => nano.use('test_polling_storage').list().then(console.log))
@LyteFM any ideas why?
I have
- nano v10.1.0
- CouchDB v3.2.2
PS if I do, for instance,
nano.auth(config.storage.couchdb.user, encodeURIComponent(config.storage.couchdb.password))
.then(() => nano.use('test_polling_storage').list().then(console.log))
I'm getting another error: Error: Name or password is incorrect.
This is a problem because Node.js's URL parser doesn't like "special characters" in the password:
i.e.
const { URL } = require('url')
const u = new URL('http://admin:g[?6asdwrF@localhost:5984')
Uncaught TypeError [ERR_INVALID_URL]: Invalid URL
at __node_internal_captureLargerStackTrace (node:internal/errors:484:5)
at new NodeError (node:internal/errors:393:5)
at URL.onParseError (node:internal/url:565:9)
at new URL (node:internal/url:645:5) {
input: 'http://admin:g[?6asdwrF@localhost:5984',
code: 'ERR_INVALID_URL'
}
a work around, as @LyteFM points out is to provide the credentials separately:
const nano = Nano('http://127.0.0.1:5984)
await nano.auth('admin', '[!5gdg@&!')
I'd be curious to know what the "URL spec" (if there is such a thing) says about putting passwords in URLs. I'm happy to fix this in Nano if there's a standard way of approaching this.
@glynnbird unfortunately, your suggestion doesn't work for me either: like I've mentioned, I've tried
const nano = nanoClient({
url: `http://127.0.0.1:5984`,
requestDefaults: {
jar: true,
},
})
nano.auth(config.storage.couchdb.user, config.storage.couchdb.password)
.then(() => nano.use('test_polling_storage').list().then(console.log))
already and looking at your suggestion, I've also tried
const nano = nanoClient(`http://127.0.0.1:5984`)
nano.auth(config.storage.couchdb.user, config.storage.couchdb.password)
.then(() => nano.use('test_polling_storage').list().then(console.log))
but I'm still getting Error: You are not authorized to access this db.
Just to be clear, I'm using TypeScript and nanoClient
is from import nanoClient from 'nano'
.
As for the "URL spec", it's actually URI, and according to wiki schemes list, those are RFC 1738, RFC 2616 (makes RFC 2068 obsolete), RFC 7230 for http: and RFC 2817, RFC 7230 for https: Looks like RFC 3986 is quite definitive (see TOC) which basically sais that..
Use of the format "user:password" in the userinfo field is deprecated
!
So, while it was a standart way to %-encode both username and password, I'm not sure what are the deprecation reasons and whether this is the way to go.
Still, I'm getting an error for what you have suggested, so I wonder what may be the problem.
As of Nano 10.1.1, you should be able to do:
nano.auth(config.storage.couchdb.user, config.storage.couchdb.password)
.then(() => nano.use('test_polling_storage').list().then(console.log))
i.e. emit username/password from the URL and use separate nano.auth
step.
I agree with your assessment that passing credentials in a URL is deprecated. Newer versions of Nano should collect credentials separately.
Awesome, after updating nano 10.1.0 → 10.1.1 this worked indeed. Thanks!
PS I've created a follow up PR, but only changed one bit in readme.md – others are to be found and updated.
@glynnbird is the method you suggested supposed to be used regularly, or nano is supposed to keep the auth alive? In my case, I'm experiencing a problem where after a successful auth at some point I'm starting to get Error: You are not authorized to access this db
. Here's the full error:
at responseHandler (<project folder>\node_modules\nano\lib\nano.js:193:20)
at <project folder>\node_modules\nano\lib\nano.js:442:13
at runMicrotasks (<anonymous>)
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
scope: 'couch',
statusCode: 401,
request: {
method: 'post',
headers: {
'content-type': 'application/json',
accept: 'application/json',
'user-agent': 'nano/10.1.1 (Node.js v16.13.1)',
'Accept-Encoding': 'deflate, gzip'
},
qsStringifyOptions: { arrayFormat: 'repeat' },
url: 'http://127.0.0.1:5984/test_polling_storage/_find',
params: undefined,
paramsSerializer: { serialize: [Function: serialize] },
data: '{"selector":{},"limit":100,"sort":[{"isoDate":"desc"}]}',
maxRedirects: 0,
httpAgent: CookieAgent {
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
defaultPort: 80,
protocol: 'http:',
options: [Object: null prototype],
requests: [Object: null prototype] {},
sockets: [Object: null prototype],
freeSockets: [Object: null prototype] {},
keepAliveMsecs: 30000,
keepAlive: true,
maxSockets: 50,
maxFreeSockets: 256,
scheduling: 'lifo',
maxTotalSockets: Infinity,
totalSocketCount: 0,
jar: [CookieJar],
[Symbol(kCapture)]: false,
[Symbol(cookieOptions)]: [Object]
},
httpsAgent: CookieAgent {
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
defaultPort: 443,
protocol: 'https:',
options: [Object: null prototype],
requests: [Object: null prototype] {},
sockets: [Object: null prototype] {},
freeSockets: [Object: null prototype] {},
keepAliveMsecs: 30000,
keepAlive: true,
maxSockets: 50,
maxFreeSockets: 256,
scheduling: 'lifo',
maxTotalSockets: Infinity,
totalSocketCount: 0,
maxCachedSessions: 100,
_sessionCache: [Object],
jar: [CookieJar],
[Symbol(kCapture)]: false,
[Symbol(cookieOptions)]: [Object]
}
},
headers: {
uri: 'http://127.0.0.1:5984/test_polling_storage/_find',
statusCode: 401,
'cache-control': 'must-revalidate',
connection: 'close',
'content-type': 'application/json',
date: 'Thu, 26 Jan 2023 07:59:45 GMT',
'x-couch-request-id': 'd2b95b849e',
'x-couchdb-body-time': '0'
},
errid: 'non_200',
description: 'You are not authorized to access this db.',
error: 'unauthorized',
reason: 'You are not authorized to access this db.'
}
Closing in favour of #324