diff --git a/CHANGELOG b/CHANGELOG index e4c68b550f..471748cc4c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ devel ----- +* added req.auth property to Foxx + * added collection.documentId method to derive document id from key * fixed internal issue #536: ArangoSearch may crash server during term lookup diff --git a/Documentation/Books/Manual/Foxx/Reference/Routers/Request.md b/Documentation/Books/Manual/Foxx/Reference/Routers/Request.md index 7691cfc846..6260b95c72 100644 --- a/Documentation/Books/Manual/Foxx/Reference/Routers/Request.md +++ b/Documentation/Books/Manual/Foxx/Reference/Routers/Request.md @@ -19,6 +19,18 @@ The request object specifies the following properties: of the ArangoDB server (e.g. `30102` for version 3.1.2) if no valid header was provided. +* **auth**: `object | null` + + The credentials supplied in the `authorization` header if any. + + If the request uses basic authentication, the value is an object like + `{basic: {username: string}}` or + `{basic: {username: string, password: string}}` or + `{basic: {}}` (if the credentials were malformed or empty). + + If the request uses bearer authentication, the value is an object like + `{bearer: string}`. + * **baseUrl**: `string` Root-relative base URL of the service, i.e. the prefix `"/_db/"` followed diff --git a/Documentation/Books/Manual/ReleaseNotes/NewFeatures35.md b/Documentation/Books/Manual/ReleaseNotes/NewFeatures35.md index 93e10e4343..cf4f94859c 100644 --- a/Documentation/Books/Manual/ReleaseNotes/NewFeatures35.md +++ b/Documentation/Books/Manual/ReleaseNotes/NewFeatures35.md @@ -448,3 +448,19 @@ The bundled JEMalloc memory allocator used in ArangoDB release packages has been upgraded from version 5.0.1 to version 5.2.0. The bundled version of the RocksDB library has been upgraded from 5.16 to 6.0. + + +Foxx +---- + +Request credentials are now exposed via the `auth` property: + +```js +const tokens = context.collection("tokens"); +router.get("/authorized", (req, res) => { + if (!req.auth || !req.auth.bearer || !tokens.exists(req.auth.bearer)) { + res.throw(403, "Not authenticated"); + } + // ... +}); +``` \ No newline at end of file diff --git a/js/server/modules/@arangodb/foxx/router/request.js b/js/server/modules/@arangodb/foxx/router/request.js index b8780a6cfe..ba55f5ea55 100644 --- a/js/server/modules/@arangodb/foxx/router/request.js +++ b/js/server/modules/@arangodb/foxx/router/request.js @@ -202,6 +202,33 @@ module.exports = return this._raw.compatibility; } + get auth () { + const header = this.get("authorization") || ""; + let match = header.match(/^Bearer (.*)$/); + if (match) { + return {bearer: match[1]}; + } + match = header.match(/^Basic (.*)$/); + if (match) { + let credentials = ""; + try { + credentials = new Buffer(match[1], "base64").toString("utf-8"); + } catch (e) {} + if (!credentials) return {basic: {}}; + const i = credentials.indexOf(":"); + if (i === -1) { + return {basic: {username: credentials}}; + } + return { + basic: { + username: credentials.slice(0, i), + password: credentials.slice(i + 1) + } + }; + } + return null; + } + get database () { return this._raw.database; } diff --git a/tests/js/server/shell/shell-foxx-request-spec.js b/tests/js/server/shell/shell-foxx-request-spec.js index a63711b471..c8162d243f 100644 --- a/tests/js/server/shell/shell-foxx-request-spec.js +++ b/tests/js/server/shell/shell-foxx-request-spec.js @@ -273,14 +273,71 @@ describe('SyntheticRequest', function () { describe('headers', function () { it('exposes the headers of the native request', function () { const headers = {}; - const rawReq = createNativeRequest({ - headers: headers - }); + const rawReq = createNativeRequest({ headers }); const req = new SyntheticRequest(rawReq, {}); expect(req.headers).to.equal(headers); }); }); + describe('auth', function () { + function btoa (str) { + return new Buffer(str).toString("base64"); + } + it('recognizes no auth', function () { + const rawReq = createNativeRequest({ headers: {} }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.auth).to.eql(null); + }); + it('recognizes bearer auth', function () { + const headers = {authorization: "Bearer deadbeef"}; + const rawReq = createNativeRequest({ headers }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.auth).to.eql({bearer: "deadbeef"}); + }); + it('recognizes empty basic auth', function () { + const headers = {authorization: `Basic ${btoa("")}`}; + const rawReq = createNativeRequest({ headers }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.auth).to.eql({basic: {}}); + }); + it('recognizes malformed basic auth', function () { + const headers = {authorization: `Basic \x00\x01`}; + const rawReq = createNativeRequest({ headers }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.auth).to.eql({basic: {}}); + }); + it('recognizes basic with no password', function () { + const username = "hello"; + const headers = {authorization: `Basic ${btoa(username)}`}; + const rawReq = createNativeRequest({ headers }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.auth).to.eql({basic: {username}}); + }); + it('recognizes basic with empty password', function () { + const username = "hello"; + const headers = {authorization: `Basic ${btoa(`${username}:`)}`}; + const rawReq = createNativeRequest({ headers }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.auth).to.eql({basic: {username, password: ""}}); + }); + it('recognizes basic with password', function () { + const username = "hello"; + const password = "world"; + const headers = {authorization: `Basic ${btoa(`${username}:${password}`)}`}; + const rawReq = createNativeRequest({ headers }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.auth).to.eql({basic: {username, password}}); + }); + it('recognizes basic with colons in password', function () { + const username = "hello"; + const password = "w:o:r:l:d"; + const headers = {authorization: `Basic ${btoa(`${username}:${password}`)}`}; + const rawReq = createNativeRequest({ headers }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.auth).to.eql({basic: {username, password}}); + }); + }); + describe('method', function () { it('exposes the method of the native request', function () { const rawReq = createNativeRequest({