Merge pull request #706 from UziTech/highlight-code-docs
This commit is contained in:
commit
67194694d0
|
@ -1,51 +1,7 @@
|
||||||
</div><!-- end #content -->
|
</div><!-- end #content -->
|
||||||
|
|
||||||
<script type="text/javascript" charset="utf-8">
|
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
|
||||||
$(function() {
|
<script type="text/javascript" src="media/js/script.js"></script>
|
||||||
var headerHeight = $("#header").height();
|
|
||||||
var offsets = [];
|
|
||||||
var current = -1;
|
|
||||||
|
|
||||||
function endpoint(scrollDistance) {
|
|
||||||
if (scrollDistance < offsets[0]) {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
for (var id = offsets.length; id > 0; id--) {
|
|
||||||
if (scrollDistance > offsets[id - 1]) {
|
|
||||||
return id - 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$("h2").each(function(i) {
|
|
||||||
offsets.push($(this).offset().top - headerHeight)
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#content").append('<h2 class="fixed" style="display: none"><span> </span></h2>');
|
|
||||||
var fixed_h2 = $("h2.fixed");
|
|
||||||
var fixed_span = $("h2.fixed span");
|
|
||||||
|
|
||||||
$("#content").scroll(function() {
|
|
||||||
var scrollDistance = $("#content").attr('scrollTop');
|
|
||||||
var now = endpoint(scrollDistance);
|
|
||||||
|
|
||||||
if (now !== current) {
|
|
||||||
$("#sidebar li").removeClass("current");
|
|
||||||
current = now;
|
|
||||||
if (current < 0) {
|
|
||||||
fixed_h2.hide();
|
|
||||||
} else if (current >= 0) {
|
|
||||||
var heading = $($("h2 span")[current]).text();
|
|
||||||
$("#sidebar a[href|=#" + heading.replace(' ', '-') + "]").parent().addClass("current");
|
|
||||||
fixed_span.text(heading);
|
|
||||||
fixed_h2.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<title>%(title)s</title>
|
<title>%(title)s</title>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
<link rel="stylesheet" type="text/css" href="media/css/style.css">
|
<link rel="stylesheet" type="text/css" href="media/css/style.css">
|
||||||
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
|
<link rel="stylesheet" type="text/css" href="media/css/highlight.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="header">
|
<div id="header">
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
var headerHeight = $("#header").height();
|
||||||
|
var offsets = [];
|
||||||
|
var current = -1;
|
||||||
|
|
||||||
|
function endpoint(scrollDistance) {
|
||||||
|
if (scrollDistance < offsets[0]) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
for (var id = offsets.length; id > 0; id--) {
|
||||||
|
if (scrollDistance > offsets[id - 1]) {
|
||||||
|
return id - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$("h2").each(function(i) {
|
||||||
|
offsets.push($(this).offset().top - headerHeight)
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#content").append('<h2 class="fixed" style="display: none"><span> </span></h2>');
|
||||||
|
var fixed_h2 = $("h2.fixed");
|
||||||
|
var fixed_span = $("h2.fixed span");
|
||||||
|
|
||||||
|
$("#content").scroll(function() {
|
||||||
|
var scrollDistance = $("#content").attr('scrollTop');
|
||||||
|
var now = endpoint(scrollDistance);
|
||||||
|
|
||||||
|
if (now !== current) {
|
||||||
|
$("#sidebar li").removeClass("current");
|
||||||
|
current = now;
|
||||||
|
if (current < 0) {
|
||||||
|
fixed_h2.hide();
|
||||||
|
} else if (current >= 0) {
|
||||||
|
var heading = $($("h2 span")[current]).text();
|
||||||
|
$("#sidebar a[href|=#" + heading.replace(' ', '-') + "]").parent().addClass("current");
|
||||||
|
fixed_span.text(heading);
|
||||||
|
fixed_h2.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
280
docs/client.md
280
docs/client.md
|
@ -15,14 +15,17 @@ with LDAP. If you're not, read the [guide](guide.html) first.
|
||||||
|
|
||||||
The code to create a new client looks like:
|
The code to create a new client looks like:
|
||||||
|
|
||||||
var ldap = require('ldapjs');
|
```js
|
||||||
var client = ldap.createClient({
|
const ldap = require('ldapjs');
|
||||||
url: ['ldap://127.0.0.1:1389', 'ldap://127.0.0.2:1389']
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('error', (err) => {
|
const client = ldap.createClient({
|
||||||
// handle connection error
|
url: ['ldap://127.0.0.1:1389', 'ldap://127.0.0.2:1389']
|
||||||
})
|
});
|
||||||
|
|
||||||
|
client.on('error', (err) => {
|
||||||
|
// handle connection error
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
You can use `ldap://` or `ldaps://`; the latter would connect over SSL (note
|
You can use `ldap://` or `ldaps://`; the latter would connect over SSL (note
|
||||||
that this will not use the LDAP TLS extended operation, but literally an SSL
|
that this will not use the LDAP TLS extended operation, but literally an SSL
|
||||||
|
@ -104,9 +107,6 @@ Almost every operation has the callback form of `function(err, res)` where err
|
||||||
will be an instance of an `LDAPError` (you can use `instanceof` to switch).
|
will be an instance of an `LDAPError` (you can use `instanceof` to switch).
|
||||||
You probably won't need to check the `res` parameter, but it's there if you do.
|
You probably won't need to check the `res` parameter, but it's there if you do.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# bind
|
# bind
|
||||||
`bind(dn, password, controls, callback)`
|
`bind(dn, password, controls, callback)`
|
||||||
|
|
||||||
|
@ -118,9 +118,11 @@ of `Control` objects. You probably don't need them though...
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
client.bind('cn=root', 'secret', function(err) {
|
```js
|
||||||
assert.ifError(err);
|
client.bind('cn=root', 'secret', (err) => {
|
||||||
});
|
assert.ifError(err);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
# add
|
# add
|
||||||
`add(dn, entry, controls, callback)`
|
`add(dn, entry, controls, callback)`
|
||||||
|
@ -132,15 +134,17 @@ controls are optional.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
var entry = {
|
```js
|
||||||
cn: 'foo',
|
const entry = {
|
||||||
sn: 'bar',
|
cn: 'foo',
|
||||||
email: ['foo@bar.com', 'foo1@bar.com'],
|
sn: 'bar',
|
||||||
objectclass: 'fooPerson'
|
email: ['foo@bar.com', 'foo1@bar.com'],
|
||||||
};
|
objectclass: 'fooPerson'
|
||||||
client.add('cn=foo, o=example', entry, function(err) {
|
};
|
||||||
assert.ifError(err);
|
client.add('cn=foo, o=example', entry, (err) => {
|
||||||
});
|
assert.ifError(err);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
# compare
|
# compare
|
||||||
`compare(dn, attribute, value, controls, callback)`
|
`compare(dn, attribute, value, controls, callback)`
|
||||||
|
@ -150,11 +154,13 @@ the entry referenced by dn.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
client.compare('cn=foo, o=example', 'sn', 'bar', function(err, matched) {
|
```js
|
||||||
assert.ifError(err);
|
client.compare('cn=foo, o=example', 'sn', 'bar', (err, matched) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
console.log('matched: ' + matched);
|
console.log('matched: ' + matched);
|
||||||
});
|
});
|
||||||
|
```
|
||||||
|
|
||||||
# del
|
# del
|
||||||
`del(dn, controls, callback)`
|
`del(dn, controls, callback)`
|
||||||
|
@ -164,9 +170,11 @@ Deletes an entry from the LDAP server.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
client.del('cn=foo, o=example', function(err) {
|
```js
|
||||||
assert.ifError(err);
|
client.del('cn=foo, o=example', (err) => {
|
||||||
});
|
assert.ifError(err);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
# exop
|
# exop
|
||||||
`exop(name, value, controls, callback)`
|
`exop(name, value, controls, callback)`
|
||||||
|
@ -178,11 +186,13 @@ should be.
|
||||||
|
|
||||||
Example (performs an LDAP 'whois' extended op):
|
Example (performs an LDAP 'whois' extended op):
|
||||||
|
|
||||||
client.exop('1.3.6.1.4.1.4203.1.11.3', function(err, value, res) {
|
```js
|
||||||
assert.ifError(err);
|
client.exop('1.3.6.1.4.1.4203.1.11.3', (err, value, res) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
console.log('whois: ' + value);
|
console.log('whois: ' + value);
|
||||||
});
|
});
|
||||||
|
```
|
||||||
|
|
||||||
# modify
|
# modify
|
||||||
`modify(name, changes, controls, callback)`
|
`modify(name, changes, controls, callback)`
|
||||||
|
@ -193,16 +203,18 @@ pass in a single `Change` or an array of `Change` objects.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
var change = new ldap.Change({
|
```js
|
||||||
operation: 'add',
|
const change = new ldap.Change({
|
||||||
modification: {
|
operation: 'add',
|
||||||
pets: ['cat', 'dog']
|
modification: {
|
||||||
}
|
pets: ['cat', 'dog']
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
client.modify('cn=foo, o=example', change, function(err) {
|
client.modify('cn=foo, o=example', change, (err) => {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
});
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## Change
|
## Change
|
||||||
|
|
||||||
|
@ -232,9 +244,11 @@ as opposed to just renaming the leaf).
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
client.modifyDN('cn=foo, o=example', 'cn=bar', function(err) {
|
```js
|
||||||
assert.ifError(err);
|
client.modifyDN('cn=foo, o=example', 'cn=bar', (err) => {
|
||||||
});
|
assert.ifError(err);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
# search
|
# search
|
||||||
`search(base, options, controls, callback)`
|
`search(base, options, controls, callback)`
|
||||||
|
@ -274,28 +288,30 @@ the code matching.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
var opts = {
|
```js
|
||||||
filter: '(&(l=Seattle)(email=*@foo.com))',
|
const opts = {
|
||||||
scope: 'sub',
|
filter: '(&(l=Seattle)(email=*@foo.com))',
|
||||||
attributes: ['dn', 'sn', 'cn']
|
scope: 'sub',
|
||||||
};
|
attributes: ['dn', 'sn', 'cn']
|
||||||
|
};
|
||||||
|
|
||||||
client.search('o=example', opts, function(err, res) {
|
client.search('o=example', opts, (err, res) => {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
|
|
||||||
res.on('searchEntry', function(entry) {
|
res.on('searchEntry', (entry) => {
|
||||||
console.log('entry: ' + JSON.stringify(entry.object));
|
console.log('entry: ' + JSON.stringify(entry.object));
|
||||||
});
|
});
|
||||||
res.on('searchReference', function(referral) {
|
res.on('searchReference', (referral) => {
|
||||||
console.log('referral: ' + referral.uris.join());
|
console.log('referral: ' + referral.uris.join());
|
||||||
});
|
});
|
||||||
res.on('error', function(err) {
|
res.on('error', (err) => {
|
||||||
console.error('error: ' + err.message);
|
console.error('error: ' + err.message);
|
||||||
});
|
});
|
||||||
res.on('end', function(result) {
|
res.on('end', (result) => {
|
||||||
console.log('status: ' + result.status);
|
console.log('status: ' + result.status);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## Filter Strings
|
## Filter Strings
|
||||||
|
|
||||||
|
@ -310,14 +326,18 @@ in prefix notation. For example, let's start simple, and build up a complicated
|
||||||
filter. The most basic filter is equality, so let's assume you want to search
|
filter. The most basic filter is equality, so let's assume you want to search
|
||||||
for an attribute `email` with a value of `foo@bar.com`. The syntax would be:
|
for an attribute `email` with a value of `foo@bar.com`. The syntax would be:
|
||||||
|
|
||||||
(email=foo@bar.com)
|
```
|
||||||
|
(email=foo@bar.com)
|
||||||
|
```
|
||||||
|
|
||||||
ldapjs requires all filters to be surrounded by '()' blocks. Ok, that was easy.
|
ldapjs requires all filters to be surrounded by '()' blocks. Ok, that was easy.
|
||||||
Let's now assume that you want to find all records where the email is actually
|
Let's now assume that you want to find all records where the email is actually
|
||||||
just anything in the "@bar.com" domain and the location attribute is set to
|
just anything in the "@bar.com" domain and the location attribute is set to
|
||||||
Seattle:
|
Seattle:
|
||||||
|
|
||||||
(&(email=*@bar.com)(l=Seattle))
|
```
|
||||||
|
(&(email=*@bar.com)(l=Seattle))
|
||||||
|
```
|
||||||
|
|
||||||
Now our filter is actually three LDAP filters. We have an `and` filter (single
|
Now our filter is actually three LDAP filters. We have an `and` filter (single
|
||||||
amp `&`), an `equality` filter `(the l=Seattle)`, and a `substring` filter.
|
amp `&`), an `equality` filter `(the l=Seattle)`, and a `substring` filter.
|
||||||
|
@ -328,7 +348,9 @@ to match any email of @bar.com or its subdomains like `"example@foo.bar.com"`.
|
||||||
Now, let's say we also want to set our filter to include a
|
Now, let's say we also want to set our filter to include a
|
||||||
specification that either the employeeType *not* be a manager nor a secretary:
|
specification that either the employeeType *not* be a manager nor a secretary:
|
||||||
|
|
||||||
(&(email=*@bar.com)(l=Seattle)(!(|(employeeType=manager)(employeeType=secretary))))
|
```
|
||||||
|
(&(email=*@bar.com)(l=Seattle)(!(|(employeeType=manager)(employeeType=secretary))))
|
||||||
|
```
|
||||||
|
|
||||||
The `not` character is represented as a `!`, the `or` as a single pipe `|`.
|
The `not` character is represented as a `!`, the `or` as a single pipe `|`.
|
||||||
It gets a little bit complicated, but it's actually quite powerful, and lets you
|
It gets a little bit complicated, but it's actually quite powerful, and lets you
|
||||||
|
@ -343,27 +365,29 @@ While callers could choose to do this manually via the `controls` parameter to
|
||||||
most simple way to use the paging automation is to set the `paged` option to
|
most simple way to use the paging automation is to set the `paged` option to
|
||||||
true when performing a search:
|
true when performing a search:
|
||||||
|
|
||||||
var opts = {
|
```js
|
||||||
filter: '(objectclass=commonobject)',
|
const opts = {
|
||||||
scope: 'sub',
|
filter: '(objectclass=commonobject)',
|
||||||
paged: true,
|
scope: 'sub',
|
||||||
sizeLimit: 200
|
paged: true,
|
||||||
};
|
sizeLimit: 200
|
||||||
client.search('o=largedir', opts, function(err, res) {
|
};
|
||||||
assert.ifError(err);
|
client.search('o=largedir', opts, (err, res) => {
|
||||||
res.on('searchEntry', function(entry) {
|
assert.ifError(err);
|
||||||
// do per-entry processing
|
res.on('searchEntry', (entry) => {
|
||||||
});
|
// do per-entry processing
|
||||||
res.on('page', function(result) {
|
});
|
||||||
console.log('page end');
|
res.on('page', (result) => {
|
||||||
});
|
console.log('page end');
|
||||||
res.on('error', function(resErr) {
|
});
|
||||||
assert.ifError(resErr);
|
res.on('error', (resErr) => {
|
||||||
});
|
assert.ifError(resErr);
|
||||||
res.on('end', function(result) {
|
});
|
||||||
console.log('done ');
|
res.on('end', (result) => {
|
||||||
});
|
console.log('done ');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
This will enable paging with a default page size of 199 (`sizeLimit` - 1) and
|
This will enable paging with a default page size of 199 (`sizeLimit` - 1) and
|
||||||
will output all of the resulting objects via the `searchEntry` event. At the
|
will output all of the resulting objects via the `searchEntry` event. At the
|
||||||
|
@ -381,32 +405,34 @@ client will wait to request the next page until that callback is executed.
|
||||||
|
|
||||||
Here is an example where both of those parameters are used:
|
Here is an example where both of those parameters are used:
|
||||||
|
|
||||||
var queue = new MyWorkQueue(someSlowWorkFunction);
|
```js
|
||||||
var opts = {
|
const queue = new MyWorkQueue(someSlowWorkFunction);
|
||||||
filter: '(objectclass=commonobject)',
|
const opts = {
|
||||||
scope: 'sub',
|
filter: '(objectclass=commonobject)',
|
||||||
paged: {
|
scope: 'sub',
|
||||||
pageSize: 250,
|
paged: {
|
||||||
pagePause: true
|
pageSize: 250,
|
||||||
},
|
pagePause: true
|
||||||
};
|
},
|
||||||
client.search('o=largerdir', opts, function(err, res) {
|
};
|
||||||
assert.ifError(err);
|
client.search('o=largerdir', opts, (err, res) => {
|
||||||
res.on('searchEntry', function(entry) {
|
assert.ifError(err);
|
||||||
// Submit incoming objects to queue
|
res.on('searchEntry', (entry) => {
|
||||||
queue.push(entry);
|
// Submit incoming objects to queue
|
||||||
});
|
queue.push(entry);
|
||||||
res.on('page', function(result, cb) {
|
});
|
||||||
// Allow the queue to flush before fetching next page
|
res.on('page', (result, cb) => {
|
||||||
queue.cbWhenFlushed(cb);
|
// Allow the queue to flush before fetching next page
|
||||||
});
|
queue.cbWhenFlushed(cb);
|
||||||
res.on('error', function(resErr) {
|
});
|
||||||
assert.ifError(resErr);
|
res.on('error', (resErr) => {
|
||||||
});
|
assert.ifError(resErr);
|
||||||
res.on('end', function(result) {
|
});
|
||||||
console.log('done');
|
res.on('end', (result) => {
|
||||||
});
|
console.log('done');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
# starttls
|
# starttls
|
||||||
`starttls(options, controls, callback)`
|
`starttls(options, controls, callback)`
|
||||||
|
@ -415,15 +441,17 @@ Attempt to secure existing LDAP connection via STARTTLS.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
var opts = {
|
```js
|
||||||
ca: [fs.readFileSync('mycacert.pem')]
|
const opts = {
|
||||||
};
|
ca: [fs.readFileSync('mycacert.pem')]
|
||||||
|
};
|
||||||
|
|
||||||
client.starttls(opts, function(err, res) {
|
client.starttls(opts, (err, res) => {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
|
|
||||||
// Client communication now TLS protected
|
// Client communication now TLS protected
|
||||||
});
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
# unbind
|
# unbind
|
||||||
|
@ -440,6 +468,8 @@ not have a response.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
client.unbind(function(err) {
|
```js
|
||||||
assert.ifError(err);
|
client.unbind((err) => {
|
||||||
});
|
assert.ifError(err);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
66
docs/dn.md
66
docs/dn.md
|
@ -26,10 +26,12 @@ The `parseDN` API converts a string representation of a DN into an ldapjs DN
|
||||||
object; in most cases this will be handled for you under the covers of the
|
object; in most cases this will be handled for you under the covers of the
|
||||||
ldapjs framework, but if you need it, it's there.
|
ldapjs framework, but if you need it, it's there.
|
||||||
|
|
||||||
var parseDN = require('ldapjs').parseDN;
|
```js
|
||||||
|
const parseDN = require('ldapjs').parseDN;
|
||||||
|
|
||||||
var dn = parseDN('cn=foo+sn=bar, ou=people, o=example');
|
const dn = parseDN('cn=foo+sn=bar, ou=people, o=example');
|
||||||
console.log(dn.toString());
|
console.log(dn.toString());
|
||||||
|
```
|
||||||
|
|
||||||
# DN
|
# DN
|
||||||
|
|
||||||
|
@ -41,40 +43,46 @@ APIs are setup to give you a DN object.
|
||||||
Returns a boolean indicating whether 'this' is a child of the passed in dn. The
|
Returns a boolean indicating whether 'this' is a child of the passed in dn. The
|
||||||
`dn` argument can be either a string or a DN.
|
`dn` argument can be either a string or a DN.
|
||||||
|
|
||||||
server.add('o=example', function(req, res, next) {
|
```js
|
||||||
if (req.dn.childOf('ou=people, o=example')) {
|
server.add('o=example', (req, res, next) => {
|
||||||
...
|
if (req.dn.childOf('ou=people, o=example')) {
|
||||||
} else {
|
...
|
||||||
...
|
} else {
|
||||||
}
|
...
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## parentOf(dn)
|
## parentOf(dn)
|
||||||
|
|
||||||
The inverse of `childOf`; returns a boolean on whether or not `this` is a parent
|
The inverse of `childOf`; returns a boolean on whether or not `this` is a parent
|
||||||
of the passed in dn. Like `childOf`, can take either a string or a DN.
|
of the passed in dn. Like `childOf`, can take either a string or a DN.
|
||||||
|
|
||||||
server.add('o=example', function(req, res, next) {
|
```js
|
||||||
var dn = parseDN('ou=people, o=example');
|
server.add('o=example', (req, res, next) => {
|
||||||
if (dn.parentOf(req.dn)) {
|
const dn = parseDN('ou=people, o=example');
|
||||||
...
|
if (dn.parentOf(req.dn)) {
|
||||||
} else {
|
...
|
||||||
...
|
} else {
|
||||||
}
|
...
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## equals(dn)
|
## equals(dn)
|
||||||
|
|
||||||
Returns a boolean indicating whether `this` is equivalent to the passed in `dn`
|
Returns a boolean indicating whether `this` is equivalent to the passed in `dn`
|
||||||
argument. `dn` can be a string or a DN.
|
argument. `dn` can be a string or a DN.
|
||||||
|
|
||||||
server.add('o=example', function(req, res, next) {
|
```js
|
||||||
if (req.dn.equals('cn=foo, ou=people, o=example')) {
|
server.add('o=example', (req, res, next) => {
|
||||||
...
|
if (req.dn.equals('cn=foo, ou=people, o=example')) {
|
||||||
} else {
|
...
|
||||||
...
|
} else {
|
||||||
}
|
...
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## parent()
|
## parent()
|
||||||
|
|
||||||
|
@ -112,6 +120,8 @@ It accepts the same parameters as `format`.
|
||||||
|
|
||||||
Returns the string representation of `this`.
|
Returns the string representation of `this`.
|
||||||
|
|
||||||
server.add('o=example', function(req, res, next) {
|
```js
|
||||||
console.log(req.dn.toString());
|
server.add('o=example', (req, res, next) => {
|
||||||
});
|
console.log(req.dn.toString());
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
|
@ -17,21 +17,23 @@ a `stack` property correctly set.
|
||||||
|
|
||||||
In general, you'll be using the errors in ldapjs like:
|
In general, you'll be using the errors in ldapjs like:
|
||||||
|
|
||||||
var ldap = require('ldapjs');
|
```js
|
||||||
|
const ldap = require('ldapjs');
|
||||||
|
|
||||||
var db = {};
|
const db = {};
|
||||||
|
|
||||||
server.add('o=example', function(req, res, next) {
|
server.add('o=example', (req, res, next) => {
|
||||||
var parent = req.dn.parent();
|
const parent = req.dn.parent();
|
||||||
if (parent) {
|
if (parent) {
|
||||||
if (!db[parent.toString()])
|
if (!db[parent.toString()])
|
||||||
return next(new ldap.NoSuchObjectError(parent.toString()));
|
return next(new ldap.NoSuchObjectError(parent.toString()));
|
||||||
}
|
}
|
||||||
if (db[req.dn.toString()])
|
if (db[req.dn.toString()])
|
||||||
return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
|
return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
|
||||||
|
|
||||||
...
|
...
|
||||||
});
|
});
|
||||||
|
```
|
||||||
|
|
||||||
I.e., if you just pass them into the `next()` handler, ldapjs will automatically
|
I.e., if you just pass them into the `next()` handler, ldapjs will automatically
|
||||||
return the appropriate LDAP error message, and stop the handler chain.
|
return the appropriate LDAP error message, and stop the handler chain.
|
||||||
|
|
955
docs/examples.md
955
docs/examples.md
File diff suppressed because it is too large
Load Diff
206
docs/filters.md
206
docs/filters.md
|
@ -34,13 +34,17 @@ Parses an [RFC2254](http://www.ietf.org/rfc/rfc2254.txt) filter string into an
|
||||||
ldapjs object(s). If the filter is "complex", it will be a "tree" of objects.
|
ldapjs object(s). If the filter is "complex", it will be a "tree" of objects.
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
var parseFilter = require('ldapjs').parseFilter;
|
```js
|
||||||
|
const parseFilter = require('ldapjs').parseFilter;
|
||||||
|
|
||||||
var f = parseFilter('(objectclass=*)');
|
const f = parseFilter('(objectclass=*)');
|
||||||
|
```
|
||||||
|
|
||||||
Is a "simple" filter, and would just return a `PresenceFilter` object. However,
|
Is a "simple" filter, and would just return a `PresenceFilter` object. However,
|
||||||
|
|
||||||
var f = parseFilter('(&(employeeType=manager)(l=Seattle))');
|
```js
|
||||||
|
const f = parseFilter('(&(employeeType=manager)(l=Seattle))');
|
||||||
|
```
|
||||||
|
|
||||||
Would return an `AndFilter`, which would have a `filters` array of two
|
Would return an `AndFilter`, which would have a `filters` array of two
|
||||||
`EqualityFilter` objects.
|
`EqualityFilter` objects.
|
||||||
|
@ -59,13 +63,15 @@ The string syntax for an equality filter is `(attr=value)`.
|
||||||
The `matches()` method will return true IFF the passed in object has a
|
The `matches()` method will return true IFF the passed in object has a
|
||||||
key matching `attribute` and a value matching `value`.
|
key matching `attribute` and a value matching `value`.
|
||||||
|
|
||||||
var f = new EqualityFilter({
|
```js
|
||||||
attribute: 'cn',
|
const f = new EqualityFilter({
|
||||||
value: 'foo'
|
attribute: 'cn',
|
||||||
});
|
value: 'foo'
|
||||||
|
});
|
||||||
|
|
||||||
f.matches({cn: 'foo'}); => true
|
f.matches({cn: 'foo'}); => true
|
||||||
f.matches({cn: 'bar'}); => false
|
f.matches({cn: 'bar'}); => false
|
||||||
|
```
|
||||||
|
|
||||||
Equality matching uses "strict" type JavaScript comparison, and by default
|
Equality matching uses "strict" type JavaScript comparison, and by default
|
||||||
everything in ldapjs (and LDAP) is a UTF-8 string. If you want comparison
|
everything in ldapjs (and LDAP) is a UTF-8 string. If you want comparison
|
||||||
|
@ -83,12 +89,14 @@ The string syntax for a presence filter is `(attr=*)`.
|
||||||
The `matches()` method will return true IFF the passed in object has a
|
The `matches()` method will return true IFF the passed in object has a
|
||||||
key matching `attribute`.
|
key matching `attribute`.
|
||||||
|
|
||||||
var f = new PresenceFilter({
|
```js
|
||||||
attribute: 'cn'
|
const f = new PresenceFilter({
|
||||||
});
|
attribute: 'cn'
|
||||||
|
});
|
||||||
|
|
||||||
f.matches({cn: 'foo'}); => true
|
f.matches({cn: 'foo'}); => true
|
||||||
f.matches({sn: 'foo'}); => false
|
f.matches({sn: 'foo'}); => false
|
||||||
|
```
|
||||||
|
|
||||||
# SubstringFilter
|
# SubstringFilter
|
||||||
|
|
||||||
|
@ -102,24 +110,28 @@ optional. The `name` property will be `substring`.
|
||||||
The string syntax for a presence filter is `(attr=foo*bar*cat*dog)`, which would
|
The string syntax for a presence filter is `(attr=foo*bar*cat*dog)`, which would
|
||||||
map to:
|
map to:
|
||||||
|
|
||||||
{
|
```js
|
||||||
initial: 'foo',
|
{
|
||||||
any: ['bar', 'cat'],
|
initial: 'foo',
|
||||||
final: 'dog'
|
any: ['bar', 'cat'],
|
||||||
}
|
final: 'dog'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
The `matches()` method will return true IFF the passed in object has a
|
The `matches()` method will return true IFF the passed in object has a
|
||||||
key matching `attribute` and the "regex" matches the value
|
key matching `attribute` and the "regex" matches the value
|
||||||
|
|
||||||
var f = new SubstringFilter({
|
```js
|
||||||
attribute: 'cn',
|
const f = new SubstringFilter({
|
||||||
initial: 'foo',
|
attribute: 'cn',
|
||||||
any: ['bar'],
|
initial: 'foo',
|
||||||
final: 'baz'
|
any: ['bar'],
|
||||||
});
|
final: 'baz'
|
||||||
|
});
|
||||||
|
|
||||||
f.matches({cn: 'foobigbardogbaz'}); => true
|
f.matches({cn: 'foobigbardogbaz'}); => true
|
||||||
f.matches({sn: 'fobigbardogbaz'}); => false
|
f.matches({sn: 'fobigbardogbaz'}); => false
|
||||||
|
```
|
||||||
|
|
||||||
# GreaterThanEqualsFilter
|
# GreaterThanEqualsFilter
|
||||||
|
|
||||||
|
@ -135,18 +147,22 @@ property and the `name` property will be `ge`.
|
||||||
|
|
||||||
The string syntax for a ge filter is:
|
The string syntax for a ge filter is:
|
||||||
|
|
||||||
(cn>=foo)
|
```
|
||||||
|
(cn>=foo)
|
||||||
|
```
|
||||||
|
|
||||||
The `matches()` method will return true IFF the passed in object has a
|
The `matches()` method will return true IFF the passed in object has a
|
||||||
key matching `attribute` and the value is `>=` this filter's `value`.
|
key matching `attribute` and the value is `>=` this filter's `value`.
|
||||||
|
|
||||||
var f = new GreaterThanEqualsFilter({
|
```js
|
||||||
attribute: 'cn',
|
const f = new GreaterThanEqualsFilter({
|
||||||
value: 'foo',
|
attribute: 'cn',
|
||||||
});
|
value: 'foo',
|
||||||
|
});
|
||||||
|
|
||||||
f.matches({cn: 'foobar'}); => true
|
f.matches({cn: 'foobar'}); => true
|
||||||
f.matches({cn: 'abc'}); => false
|
f.matches({cn: 'abc'}); => false
|
||||||
|
```
|
||||||
|
|
||||||
# LessThanEqualsFilter
|
# LessThanEqualsFilter
|
||||||
|
|
||||||
|
@ -159,7 +175,9 @@ Note that the ldapjs schema middleware will do this.
|
||||||
|
|
||||||
The string syntax for a le filter is:
|
The string syntax for a le filter is:
|
||||||
|
|
||||||
(cn<=foo)
|
```
|
||||||
|
(cn<=foo)
|
||||||
|
```
|
||||||
|
|
||||||
The LessThanEqualsFilter will have an `attribute` property, a `value`
|
The LessThanEqualsFilter will have an `attribute` property, a `value`
|
||||||
property and the `name` property will be `le`.
|
property and the `name` property will be `le`.
|
||||||
|
@ -167,13 +185,15 @@ property and the `name` property will be `le`.
|
||||||
The `matches()` method will return true IFF the passed in object has a
|
The `matches()` method will return true IFF the passed in object has a
|
||||||
key matching `attribute` and the value is `<=` this filter's `value`.
|
key matching `attribute` and the value is `<=` this filter's `value`.
|
||||||
|
|
||||||
var f = new LessThanEqualsFilter({
|
```js
|
||||||
attribute: 'cn',
|
const f = new LessThanEqualsFilter({
|
||||||
value: 'foo',
|
attribute: 'cn',
|
||||||
});
|
value: 'foo',
|
||||||
|
});
|
||||||
|
|
||||||
f.matches({cn: 'abc'}); => true
|
f.matches({cn: 'abc'}); => true
|
||||||
f.matches({cn: 'foobar'}); => false
|
f.matches({cn: 'foobar'}); => false
|
||||||
|
```
|
||||||
|
|
||||||
# AndFilter
|
# AndFilter
|
||||||
|
|
||||||
|
@ -184,26 +204,30 @@ object will have a `filters` property which is an array of `Filter` objects. The
|
||||||
The string syntax for an and filter is (assuming below we're and'ing two
|
The string syntax for an and filter is (assuming below we're and'ing two
|
||||||
equality filters):
|
equality filters):
|
||||||
|
|
||||||
(&(cn=foo)(sn=bar))
|
```
|
||||||
|
(&(cn=foo)(sn=bar))
|
||||||
|
```
|
||||||
|
|
||||||
The `matches()` method will return true IFF the passed in object matches all
|
The `matches()` method will return true IFF the passed in object matches all
|
||||||
the filters in the `filters` array.
|
the filters in the `filters` array.
|
||||||
|
|
||||||
var f = new AndFilter({
|
```js
|
||||||
filters: [
|
const f = new AndFilter({
|
||||||
new EqualityFilter({
|
filters: [
|
||||||
attribute: 'cn',
|
new EqualityFilter({
|
||||||
value: 'foo'
|
attribute: 'cn',
|
||||||
}),
|
value: 'foo'
|
||||||
new EqualityFilter({
|
}),
|
||||||
attribute: 'sn',
|
new EqualityFilter({
|
||||||
value: 'bar'
|
attribute: 'sn',
|
||||||
})
|
value: 'bar'
|
||||||
]
|
})
|
||||||
});
|
]
|
||||||
|
});
|
||||||
|
|
||||||
f.matches({cn: 'foo', sn: 'bar'}); => true
|
f.matches({cn: 'foo', sn: 'bar'}); => true
|
||||||
f.matches({cn: 'foo', sn: 'baz'}); => false
|
f.matches({cn: 'foo', sn: 'baz'}); => false
|
||||||
|
```
|
||||||
|
|
||||||
# OrFilter
|
# OrFilter
|
||||||
|
|
||||||
|
@ -214,26 +238,30 @@ object will have a `filters` property which is an array of `Filter` objects. The
|
||||||
The string syntax for an or filter is (assuming below we're or'ing two
|
The string syntax for an or filter is (assuming below we're or'ing two
|
||||||
equality filters):
|
equality filters):
|
||||||
|
|
||||||
(|(cn=foo)(sn=bar))
|
```
|
||||||
|
(|(cn=foo)(sn=bar))
|
||||||
|
```
|
||||||
|
|
||||||
The `matches()` method will return true IFF the passed in object matches *any*
|
The `matches()` method will return true IFF the passed in object matches *any*
|
||||||
of the filters in the `filters` array.
|
of the filters in the `filters` array.
|
||||||
|
|
||||||
var f = new OrFilter({
|
```js
|
||||||
filters: [
|
const f = new OrFilter({
|
||||||
new EqualityFilter({
|
filters: [
|
||||||
attribute: 'cn',
|
new EqualityFilter({
|
||||||
value: 'foo'
|
attribute: 'cn',
|
||||||
}),
|
value: 'foo'
|
||||||
new EqualityFilter({
|
}),
|
||||||
attribute: 'sn',
|
new EqualityFilter({
|
||||||
value: 'bar'
|
attribute: 'sn',
|
||||||
})
|
value: 'bar'
|
||||||
]
|
})
|
||||||
});
|
]
|
||||||
|
});
|
||||||
|
|
||||||
f.matches({cn: 'foo', sn: 'baz'}); => true
|
f.matches({cn: 'foo', sn: 'baz'}); => true
|
||||||
f.matches({cn: 'bar', sn: 'baz'}); => false
|
f.matches({cn: 'bar', sn: 'baz'}); => false
|
||||||
|
```
|
||||||
|
|
||||||
# NotFilter
|
# NotFilter
|
||||||
|
|
||||||
|
@ -244,20 +272,24 @@ The `name` property will be `not`.
|
||||||
The string syntax for a not filter is (assuming below we're not'ing an
|
The string syntax for a not filter is (assuming below we're not'ing an
|
||||||
equality filter):
|
equality filter):
|
||||||
|
|
||||||
(!(cn=foo))
|
```
|
||||||
|
(!(cn=foo))
|
||||||
|
```
|
||||||
|
|
||||||
The `matches()` method will return true IFF the passed in object does not match
|
The `matches()` method will return true IFF the passed in object does not match
|
||||||
the filter in the `filter` property.
|
the filter in the `filter` property.
|
||||||
|
|
||||||
var f = new NotFilter({
|
```js
|
||||||
filter: new EqualityFilter({
|
const f = new NotFilter({
|
||||||
attribute: 'cn',
|
filter: new EqualityFilter({
|
||||||
value: 'foo'
|
attribute: 'cn',
|
||||||
})
|
value: 'foo'
|
||||||
});
|
})
|
||||||
|
});
|
||||||
|
|
||||||
f.matches({cn: 'bar'}); => true
|
f.matches({cn: 'bar'}); => true
|
||||||
f.matches({cn: 'foo'}); => false
|
f.matches({cn: 'foo'}); => false
|
||||||
|
```
|
||||||
|
|
||||||
# ApproximateFilter
|
# ApproximateFilter
|
||||||
|
|
||||||
|
@ -274,10 +306,12 @@ The string syntax for an equality filter is `(attr~=value)`.
|
||||||
The `matches()` method will return true IFF the passed in object has a
|
The `matches()` method will return true IFF the passed in object has a
|
||||||
key matching `attribute` and a value exactly matching `value`.
|
key matching `attribute` and a value exactly matching `value`.
|
||||||
|
|
||||||
var f = new ApproximateFilter({
|
```js
|
||||||
attribute: 'cn',
|
const f = new ApproximateFilter({
|
||||||
value: 'foo'
|
attribute: 'cn',
|
||||||
});
|
value: 'foo'
|
||||||
|
});
|
||||||
|
|
||||||
f.matches({cn: 'foo'}); => true
|
f.matches({cn: 'foo'}); => true
|
||||||
f.matches({cn: 'bar'}); => false
|
f.matches({cn: 'bar'}); => false
|
||||||
|
```
|
||||||
|
|
541
docs/guide.md
541
docs/guide.md
|
@ -31,23 +31,26 @@ Access Protocol". A directory service basically breaks down as follows:
|
||||||
|
|
||||||
It might be helpful to visualize:
|
It might be helpful to visualize:
|
||||||
|
|
||||||
o=example
|
```
|
||||||
/ \
|
o=example
|
||||||
ou=users ou=groups
|
/ \
|
||||||
/ | | \
|
ou=users ou=groups
|
||||||
cn=john cn=jane cn=dudes cn=dudettes
|
/ | | \
|
||||||
/
|
cn=john cn=jane cn=dudes cn=dudettes
|
||||||
keyid=foo
|
/
|
||||||
|
keyid=foo
|
||||||
|
```
|
||||||
|
|
||||||
Let's say we wanted to look at the record cn=john:
|
Let's say we wanted to look at the record cn=john:
|
||||||
|
|
||||||
dn: cn=john, ou=users, o=example
|
```shell
|
||||||
cn: john
|
dn: cn=john, ou=users, o=example
|
||||||
sn: smith
|
cn: john
|
||||||
email: john@example.com
|
sn: smith
|
||||||
email: john.smith@example.com
|
email: john@example.com
|
||||||
objectClass: person
|
email: john.smith@example.com
|
||||||
|
objectClass: person
|
||||||
|
```
|
||||||
|
|
||||||
A few things to note:
|
A few things to note:
|
||||||
|
|
||||||
|
@ -111,7 +114,9 @@ If you don't already have node.js and npm, clearly you need those, so follow
|
||||||
the steps at [nodejs.org](http://nodejs.org) and [npmjs.org](http://npmjs.org),
|
the steps at [nodejs.org](http://nodejs.org) and [npmjs.org](http://npmjs.org),
|
||||||
respectively. After that, run:
|
respectively. After that, run:
|
||||||
|
|
||||||
$ npm install ldapjs
|
```shell
|
||||||
|
$ npm install ldapjs
|
||||||
|
```
|
||||||
|
|
||||||
Rather than overload you with client-side programming for now, we'll use
|
Rather than overload you with client-side programming for now, we'll use
|
||||||
the OpenLDAP CLI to interact with our server. It's almost certainly already
|
the OpenLDAP CLI to interact with our server. It's almost certainly already
|
||||||
|
@ -121,18 +126,22 @@ package manager here.
|
||||||
To get started, open some file, and let's get the library loaded and a server
|
To get started, open some file, and let's get the library loaded and a server
|
||||||
created:
|
created:
|
||||||
|
|
||||||
var ldap = require('ldapjs');
|
```js
|
||||||
|
const ldap = require('ldapjs');
|
||||||
|
|
||||||
var server = ldap.createServer();
|
const server = ldap.createServer();
|
||||||
|
|
||||||
server.listen(1389, function() {
|
server.listen(1389, () => {
|
||||||
console.log('/etc/passwd LDAP server up at: %s', server.url);
|
console.log('/etc/passwd LDAP server up at: %s', server.url);
|
||||||
});
|
});
|
||||||
|
```
|
||||||
|
|
||||||
And run that. Doing anything will give you errors (LDAP "No Such Object")
|
And run that. Doing anything will give you errors (LDAP "No Such Object")
|
||||||
since we haven't added any support in yet, but go ahead and try it anyway:
|
since we haven't added any support in yet, but go ahead and try it anyway:
|
||||||
|
|
||||||
$ ldapsearch -H ldap://localhost:1389 -x -b "o=myhost" objectclass=*
|
```shell
|
||||||
|
$ ldapsearch -H ldap://localhost:1389 -x -b "o=myhost" objectclass=*
|
||||||
|
```
|
||||||
|
|
||||||
Before we go any further, note that the complete code for the server we are
|
Before we go any further, note that the complete code for the server we are
|
||||||
about to build up is on the [examples](examples.html) page.
|
about to build up is on the [examples](examples.html) page.
|
||||||
|
@ -153,13 +162,15 @@ has no correspondence to our Unix root user, it's just something we're making up
|
||||||
and going to use for allowing an (LDAP) admin to do anything. To do so, add
|
and going to use for allowing an (LDAP) admin to do anything. To do so, add
|
||||||
this code into your file:
|
this code into your file:
|
||||||
|
|
||||||
server.bind('cn=root', function(req, res, next) {
|
```js
|
||||||
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
|
server.bind('cn=root', (req, res, next) => {
|
||||||
return next(new ldap.InvalidCredentialsError());
|
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
|
||||||
|
return next(new ldap.InvalidCredentialsError());
|
||||||
|
|
||||||
res.end();
|
res.end();
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
```
|
||||||
|
|
||||||
Not very secure, but this is a demo. What we did there was "mount" a tree in
|
Not very secure, but this is a demo. What we did there was "mount" a tree in
|
||||||
the ldapjs server, and add a handler for the _bind_ method. If you've ever used
|
the ldapjs server, and add a handler for the _bind_ method. If you've ever used
|
||||||
|
@ -168,7 +179,9 @@ handlers in, as we'll see later.
|
||||||
|
|
||||||
On to the meat of the method. What's up with this?
|
On to the meat of the method. What's up with this?
|
||||||
|
|
||||||
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
|
```js
|
||||||
|
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
|
||||||
|
```
|
||||||
|
|
||||||
The first part `req.dn.toString() !== 'cn=root'`: you're probably thinking
|
The first part `req.dn.toString() !== 'cn=root'`: you're probably thinking
|
||||||
"WTF?!? Does ldapjs allow something other than cn=root into this handler?" Sort
|
"WTF?!? Does ldapjs allow something other than cn=root into this handler?" Sort
|
||||||
|
@ -192,18 +205,22 @@ add another handler in later you won't get bit by it not being invoked.
|
||||||
|
|
||||||
Blah blah, let's try running the ldap client again, first with a bad password:
|
Blah blah, let's try running the ldap client again, first with a bad password:
|
||||||
|
|
||||||
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w foo -b "o=myhost" objectclass=*
|
```shell
|
||||||
|
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w foo -b "o=myhost" objectclass=*
|
||||||
|
|
||||||
ldap_bind: Invalid credentials (49)
|
ldap_bind: Invalid credentials (49)
|
||||||
matched DN: cn=root
|
matched DN: cn=root
|
||||||
additional info: Invalid Credentials
|
additional info: Invalid Credentials
|
||||||
|
```
|
||||||
|
|
||||||
And again with the correct one:
|
And again with the correct one:
|
||||||
|
|
||||||
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" objectclass=*
|
```shell
|
||||||
|
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" objectclass=*
|
||||||
|
|
||||||
No such object (32)
|
No such object (32)
|
||||||
Additional information: No tree found for: o=myhost
|
Additional information: No tree found for: o=myhost
|
||||||
|
```
|
||||||
|
|
||||||
Don't worry about all the flags we're passing into OpenLDAP, that's just to make
|
Don't worry about all the flags we're passing into OpenLDAP, that's just to make
|
||||||
their CLI less annonyingly noisy. This time, we got another `No such object`
|
their CLI less annonyingly noisy. This time, we got another `No such object`
|
||||||
|
@ -217,12 +234,14 @@ what if the remote end doesn't authenticate at all? Right, nothing says they
|
||||||
*have to* bind, that's just what the common clients do. Let's add a quick
|
*have to* bind, that's just what the common clients do. Let's add a quick
|
||||||
authorization handler that we'll use in all our subsequent routes:
|
authorization handler that we'll use in all our subsequent routes:
|
||||||
|
|
||||||
function authorize(req, res, next) {
|
```js
|
||||||
if (!req.connection.ldap.bindDN.equals('cn=root'))
|
function authorize(req, res, next) {
|
||||||
return next(new ldap.InsufficientAccessRightsError());
|
if (!req.connection.ldap.bindDN.equals('cn=root'))
|
||||||
|
return next(new ldap.InsufficientAccessRightsError());
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Should be pretty self-explanatory, but as a reminder, LDAP is connection
|
Should be pretty self-explanatory, but as a reminder, LDAP is connection
|
||||||
oriented, so we check that the connection remote user was indeed our `cn=root`
|
oriented, so we check that the connection remote user was indeed our `cn=root`
|
||||||
|
@ -233,7 +252,9 @@ oriented, so we check that the connection remote user was indeed our `cn=root`
|
||||||
We said we wanted to allow LDAP operations over /etc/passwd, so let's detour
|
We said we wanted to allow LDAP operations over /etc/passwd, so let's detour
|
||||||
for a moment to explain an /etc/passwd record.
|
for a moment to explain an /etc/passwd record.
|
||||||
|
|
||||||
jsmith:x:1001:1000:Joe Smith,Room 1007,(234)555-8910,(234)555-0044,email:/home/jsmith:/bin/sh
|
```shell
|
||||||
|
jsmith:x:1001:1000:Joe Smith,Room 1007,(234)555-8910,(234)555-0044,email:/home/jsmith:/bin/sh
|
||||||
|
```
|
||||||
|
|
||||||
The sample record above maps to:
|
The sample record above maps to:
|
||||||
|
|
||||||
|
@ -248,77 +269,86 @@ The sample record above maps to:
|
||||||
|/bin/sh |Shell |
|
|/bin/sh |Shell |
|
||||||
|
|
||||||
Let's write some handlers to parse that and transform it into an LDAP search
|
Let's write some handlers to parse that and transform it into an LDAP search
|
||||||
record (note, you'll need to add `var fs = require('fs');` at the top of the
|
record (note, you'll need to add `const fs = require('fs');` at the top of the
|
||||||
source file).
|
source file).
|
||||||
|
|
||||||
First, make a handler that just loads the "user database" in a "pre" handler:
|
First, make a handler that just loads the "user database" in a "pre" handler:
|
||||||
|
|
||||||
function loadPasswdFile(req, res, next) {
|
```js
|
||||||
fs.readFile('/etc/passwd', 'utf8', function(err, data) {
|
function loadPasswdFile(req, res, next) {
|
||||||
if (err)
|
fs.readFile('/etc/passwd', 'utf8', (err, data) => {
|
||||||
return next(new ldap.OperationsError(err.message));
|
if (err)
|
||||||
|
return next(new ldap.OperationsError(err.message));
|
||||||
|
|
||||||
req.users = {};
|
req.users = {};
|
||||||
|
|
||||||
var lines = data.split('\n');
|
const lines = data.split('\n');
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (const line of lines) {
|
||||||
if (!lines[i] || /^#/.test(lines[i]))
|
if (!line || /^#/.test(line))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var record = lines[i].split(':');
|
const record = line.split(':');
|
||||||
if (!record || !record.length)
|
if (!record || !record.length)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
req.users[record[0]] = {
|
req.users[record[0]] = {
|
||||||
dn: 'cn=' + record[0] + ', ou=users, o=myhost',
|
dn: 'cn=' + record[0] + ', ou=users, o=myhost',
|
||||||
attributes: {
|
attributes: {
|
||||||
cn: record[0],
|
cn: record[0],
|
||||||
uid: record[2],
|
uid: record[2],
|
||||||
gid: record[3],
|
gid: record[3],
|
||||||
description: record[4],
|
description: record[4],
|
||||||
homedirectory: record[5],
|
homedirectory: record[5],
|
||||||
shell: record[6] || '',
|
shell: record[6] || '',
|
||||||
objectclass: 'unixUser'
|
objectclass: 'unixUser'
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
return next();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Ok, all that did is tack the /etc/passwd records onto req.users so that any
|
Ok, all that did is tack the /etc/passwd records onto req.users so that any
|
||||||
subsequent handler doesn't have to reload the file. Next, let's write a search
|
subsequent handler doesn't have to reload the file. Next, let's write a search
|
||||||
handler to process that:
|
handler to process that:
|
||||||
|
|
||||||
var pre = [authorize, loadPasswdFile];
|
```js
|
||||||
|
const pre = [authorize, loadPasswdFile];
|
||||||
|
|
||||||
server.search('o=myhost', pre, function(req, res, next) {
|
server.search('o=myhost', pre, (req, res, next) => {
|
||||||
Object.keys(req.users).forEach(function(k) {
|
const keys = Object.keys(req.users);
|
||||||
if (req.filter.matches(req.users[k].attributes))
|
for (const k of keys) {
|
||||||
res.send(req.users[k]);
|
if (req.filter.matches(req.users[k].attributes))
|
||||||
});
|
res.send(req.users[k]);
|
||||||
|
}
|
||||||
|
|
||||||
res.end();
|
res.end();
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
```
|
||||||
|
|
||||||
And try running:
|
And try running:
|
||||||
|
|
||||||
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" cn=root
|
```shell
|
||||||
dn: cn=root, ou=users, o=myhost
|
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" cn=root
|
||||||
cn: root
|
dn: cn=root, ou=users, o=myhost
|
||||||
uid: 0
|
cn: root
|
||||||
gid: 0
|
uid: 0
|
||||||
description: System Administrator
|
gid: 0
|
||||||
homedirectory: /var/root
|
description: System Administrator
|
||||||
shell: /bin/sh
|
homedirectory: /var/root
|
||||||
objectclass: unixUser
|
shell: /bin/sh
|
||||||
|
objectclass: unixUser
|
||||||
|
```
|
||||||
|
|
||||||
Sweet! Try this out too:
|
Sweet! Try this out too:
|
||||||
|
|
||||||
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" objectclass=*
|
```shell
|
||||||
...
|
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" objectclass=*
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
You should have seen an entry for every record in /etc/passwd with the second.
|
You should have seen an entry for every record in /etc/passwd with the second.
|
||||||
What all did we do here? A lot. Let's break this down...
|
What all did we do here? A lot. Let's break this down...
|
||||||
|
@ -327,7 +357,9 @@ What all did we do here? A lot. Let's break this down...
|
||||||
|
|
||||||
Let's start with looking at what you even asked for:
|
Let's start with looking at what you even asked for:
|
||||||
|
|
||||||
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" cn=root
|
```shell
|
||||||
|
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" cn=root
|
||||||
|
```
|
||||||
|
|
||||||
We can throw away `ldapsearch -H -x -D -w -LLL`, as those just specify the URL
|
We can throw away `ldapsearch -H -x -D -w -LLL`, as those just specify the URL
|
||||||
to connect to, the bind credentials and the `-LLL` just quiets down OpenLDAP.
|
to connect to, the bind credentials and the `-LLL` just quiets down OpenLDAP.
|
||||||
|
@ -360,18 +392,20 @@ and made the cheesiest transform ever, which is making up a "search entry." A
|
||||||
search entry _must_ have a DN so the client knows what record it is, and a set
|
search entry _must_ have a DN so the client knows what record it is, and a set
|
||||||
of attributes. So that's why we did this:
|
of attributes. So that's why we did this:
|
||||||
|
|
||||||
var entry = {
|
```js
|
||||||
dn: 'cn=' + record[0] + ', ou=users, o=myhost',
|
const entry = {
|
||||||
attributes: {
|
dn: 'cn=' + record[0] + ', ou=users, o=myhost',
|
||||||
cn: record[0],
|
attributes: {
|
||||||
uid: record[2],
|
cn: record[0],
|
||||||
gid: record[3],
|
uid: record[2],
|
||||||
description: record[4],
|
gid: record[3],
|
||||||
homedirectory: record[5],
|
description: record[4],
|
||||||
shell: record[6] || '',
|
homedirectory: record[5],
|
||||||
objectclass: 'unixUser'
|
shell: record[6] || '',
|
||||||
}
|
objectclass: 'unixUser'
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
Next, we let ldapjs do all the hard work of figuring out LDAP search filters
|
Next, we let ldapjs do all the hard work of figuring out LDAP search filters
|
||||||
for us by calling `req.filter.matches`. If it matched, we return the whole
|
for us by calling `req.filter.matches`. If it matched, we return the whole
|
||||||
|
@ -386,120 +420,133 @@ shell set to `/bin/false` and whose name starts with `p` (I'm doing this
|
||||||
on Ubuntu). Then, let's say we only care about their login name and primary
|
on Ubuntu). Then, let's say we only care about their login name and primary
|
||||||
group id. We'd do this:
|
group id. We'd do this:
|
||||||
|
|
||||||
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" "(&(shell=/bin/false)(cn=p*))" cn gid
|
```shell
|
||||||
dn: cn=proxy, ou=users, o=myhost
|
$ ldapsearch -H ldap://localhost:1389 -x -D cn=root -w secret -LLL -b "o=myhost" "(&(shell=/bin/false)(cn=p*))" cn gid
|
||||||
cn: proxy
|
dn: cn=proxy, ou=users, o=myhost
|
||||||
gid: 13
|
cn: proxy
|
||||||
|
gid: 13
|
||||||
|
|
||||||
dn: cn=pulse, ou=users, o=myhost
|
dn: cn=pulse, ou=users, o=myhost
|
||||||
cn: pulse
|
cn: pulse
|
||||||
gid: 114
|
gid: 114
|
||||||
|
```
|
||||||
|
|
||||||
## Add
|
## Add
|
||||||
|
|
||||||
This is going to be a little bit ghetto, since what we're going to do is just
|
This is going to be a little bit ghetto, since what we're going to do is just
|
||||||
use node's child process module to spawn calls to `adduser`. Go ahead and add
|
use node's child process module to spawn calls to `adduser`. Go ahead and add
|
||||||
the following code in as another handler (you'll need a
|
the following code in as another handler (you'll need a
|
||||||
`var spawn = require('child_process').spawn;` at the top of your file):
|
`const { spawn } = require('child_process');` at the top of your file):
|
||||||
|
|
||||||
server.add('ou=users, o=myhost', pre, function(req, res, next) {
|
```js
|
||||||
if (!req.dn.rdns[0].attrs.cn)
|
server.add('ou=users, o=myhost', pre, (req, res, next) => {
|
||||||
return next(new ldap.ConstraintViolationError('cn required'));
|
if (!req.dn.rdns[0].attrs.cn)
|
||||||
|
return next(new ldap.ConstraintViolationError('cn required'));
|
||||||
|
|
||||||
if (req.users[req.dn.rdns[0].attrs.cn.value])
|
if (req.users[req.dn.rdns[0].attrs.cn.value])
|
||||||
return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
|
return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
|
||||||
|
|
||||||
var entry = req.toObject().attributes;
|
const entry = req.toObject().attributes;
|
||||||
|
|
||||||
if (entry.objectclass.indexOf('unixUser') === -1)
|
if (entry.objectclass.indexOf('unixUser') === -1)
|
||||||
return next(new ldap.ConstraintViolationError('entry must be a unixUser'));
|
return next(new ldap.ConstraintViolationError('entry must be a unixUser'));
|
||||||
|
|
||||||
var opts = ['-m'];
|
const opts = ['-m'];
|
||||||
if (entry.description) {
|
if (entry.description) {
|
||||||
opts.push('-c');
|
opts.push('-c');
|
||||||
opts.push(entry.description[0]);
|
opts.push(entry.description[0]);
|
||||||
}
|
}
|
||||||
if (entry.homedirectory) {
|
if (entry.homedirectory) {
|
||||||
opts.push('-d');
|
opts.push('-d');
|
||||||
opts.push(entry.homedirectory[0]);
|
opts.push(entry.homedirectory[0]);
|
||||||
}
|
}
|
||||||
if (entry.gid) {
|
if (entry.gid) {
|
||||||
opts.push('-g');
|
opts.push('-g');
|
||||||
opts.push(entry.gid[0]);
|
opts.push(entry.gid[0]);
|
||||||
}
|
}
|
||||||
if (entry.shell) {
|
if (entry.shell) {
|
||||||
opts.push('-s');
|
opts.push('-s');
|
||||||
opts.push(entry.shell[0]);
|
opts.push(entry.shell[0]);
|
||||||
}
|
}
|
||||||
if (entry.uid) {
|
if (entry.uid) {
|
||||||
opts.push('-u');
|
opts.push('-u');
|
||||||
opts.push(entry.uid[0]);
|
opts.push(entry.uid[0]);
|
||||||
}
|
}
|
||||||
opts.push(entry.cn[0]);
|
opts.push(entry.cn[0]);
|
||||||
var useradd = spawn('useradd', opts);
|
const useradd = spawn('useradd', opts);
|
||||||
|
|
||||||
var messages = [];
|
const messages = [];
|
||||||
|
|
||||||
useradd.stdout.on('data', function(data) {
|
useradd.stdout.on('data', (data) => {
|
||||||
messages.push(data.toString());
|
messages.push(data.toString());
|
||||||
});
|
});
|
||||||
useradd.stderr.on('data', function(data) {
|
useradd.stderr.on('data', (data) => {
|
||||||
messages.push(data.toString());
|
messages.push(data.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
useradd.on('exit', function(code) {
|
useradd.on('exit', (code) => {
|
||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
var msg = '' + code;
|
let msg = '' + code;
|
||||||
if (messages.length)
|
if (messages.length)
|
||||||
msg += ': ' + messages.join();
|
msg += ': ' + messages.join();
|
||||||
return next(new ldap.OperationsError(msg));
|
return next(new ldap.OperationsError(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
res.end();
|
res.end();
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
```
|
||||||
|
|
||||||
Then, you'll need to be root to have this running, so start your server with
|
Then, you'll need to be root to have this running, so start your server with
|
||||||
`sudo` (or be root, whatever). Now, go ahead and create a file called
|
`sudo` (or be root, whatever). Now, go ahead and create a file called
|
||||||
`user.ldif` with the following contents:
|
`user.ldif` with the following contents:
|
||||||
|
|
||||||
dn: cn=ldapjs, ou=users, o=myhost
|
```shell
|
||||||
objectClass: unixUser
|
dn: cn=ldapjs, ou=users, o=myhost
|
||||||
cn: ldapjs
|
objectClass: unixUser
|
||||||
shell: /bin/bash
|
cn: ldapjs
|
||||||
description: Created via ldapadd
|
shell: /bin/bash
|
||||||
|
description: Created via ldapadd
|
||||||
|
```
|
||||||
|
|
||||||
Now go ahead and invoke with:
|
Now go ahead and invoke with:
|
||||||
|
|
||||||
$ ldapadd -H ldap://localhost:1389 -x -D cn=root -w secret -f ./user.ldif
|
```shell
|
||||||
adding new entry "cn=ldapjs, ou=users, o=myhost"
|
$ ldapadd -H ldap://localhost:1389 -x -D cn=root -w secret -f ./user.ldif
|
||||||
|
adding new entry "cn=ldapjs, ou=users, o=myhost"
|
||||||
|
```
|
||||||
|
|
||||||
Let's confirm he got added with an ldapsearch:
|
Let's confirm he got added with an ldapsearch:
|
||||||
|
|
||||||
$ ldapsearch -H ldap://localhost:1389 -LLL -x -D cn=root -w secret -b "ou=users, o=myhost" cn=ldapjs
|
```shell
|
||||||
dn: cn=ldapjs, ou=users, o=myhost
|
$ ldapsearch -H ldap://localhost:1389 -LLL -x -D cn=root -w secret -b "ou=users, o=myhost" cn=ldapjs
|
||||||
cn: ldapjs
|
dn: cn=ldapjs, ou=users, o=myhost
|
||||||
uid: 1001
|
cn: ldapjs
|
||||||
gid: 1001
|
uid: 1001
|
||||||
description: Created via ldapadd
|
gid: 1001
|
||||||
homedirectory: /home/ldapjs
|
description: Created via ldapadd
|
||||||
shell: /bin/bash
|
homedirectory: /home/ldapjs
|
||||||
objectclass: unixUser
|
shell: /bin/bash
|
||||||
|
objectclass: unixUser
|
||||||
|
```
|
||||||
|
|
||||||
As before, here's a breakdown of the code:
|
As before, here's a breakdown of the code:
|
||||||
|
|
||||||
server.add('ou=users, o=myhost', pre, function(req, res, next) {
|
```js
|
||||||
if (!req.dn.rdns[0].attrs.cn)
|
server.add('ou=users, o=myhost', pre, (req, res, next) => {
|
||||||
return next(new ldap.ConstraintViolationError('cn required'));
|
if (!req.dn.rdns[0].attrs.cn)
|
||||||
|
return next(new ldap.ConstraintViolationError('cn required'));
|
||||||
|
|
||||||
if (req.users[req.dn.rdns[0].attrs.cn.value])
|
if (req.users[req.dn.rdns[0].attrs.cn.value])
|
||||||
return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
|
return next(new ldap.EntryAlreadyExistsError(req.dn.toString()));
|
||||||
|
|
||||||
var entry = req.toObject().attributes;
|
const entry = req.toObject().attributes;
|
||||||
|
|
||||||
if (entry.objectclass.indexOf('unixUser') === -1)
|
if (entry.objectclass.indexOf('unixUser') === -1)
|
||||||
return next(new ldap.ConstraintViolationError('entry must be a unixUser'));
|
return next(new ldap.ConstraintViolationError('entry must be a unixUser'));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
A few new things:
|
A few new things:
|
||||||
|
|
||||||
|
@ -534,42 +581,43 @@ Unlike HTTP, "partial" document updates are fully specified as part of the
|
||||||
RFC, so appending, removing, or replacing a single attribute is pretty natural.
|
RFC, so appending, removing, or replacing a single attribute is pretty natural.
|
||||||
Go ahead and add the following code into your source file:
|
Go ahead and add the following code into your source file:
|
||||||
|
|
||||||
server.modify('ou=users, o=myhost', pre, function(req, res, next) {
|
```js
|
||||||
if (!req.dn.rdns[0].attrs.cn || !req.users[req.dn.rdns[0].attrs.cn.value])
|
server.modify('ou=users, o=myhost', pre, (req, res, next) => {
|
||||||
return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
if (!req.dn.rdns[0].attrs.cn || !req.users[req.dn.rdns[0].attrs.cn.value])
|
||||||
|
return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||||
|
|
||||||
if (!req.changes.length)
|
if (!req.changes.length)
|
||||||
return next(new ldap.ProtocolError('changes required'));
|
return next(new ldap.ProtocolError('changes required'));
|
||||||
|
|
||||||
var user = req.users[req.dn.rdns[0].attrs.cn.value].attributes;
|
const user = req.users[req.dn.rdns[0].attrs.cn.value].attributes;
|
||||||
var mod;
|
let mod;
|
||||||
|
|
||||||
for (var i = 0; i < req.changes.length; i++) {
|
for (const i = 0; i < req.changes.length; i++) {
|
||||||
mod = req.changes[i].modification;
|
mod = req.changes[i].modification;
|
||||||
switch (req.changes[i].operation) {
|
switch (req.changes[i].operation) {
|
||||||
case 'replace':
|
case 'replace':
|
||||||
if (mod.type !== 'userpassword' || !mod.vals || !mod.vals.length)
|
if (mod.type !== 'userpassword' || !mod.vals || !mod.vals.length)
|
||||||
return next(new ldap.UnwillingToPerformError('only password updates ' +
|
return next(new ldap.UnwillingToPerformError('only password updates ' +
|
||||||
'allowed'));
|
'allowed'));
|
||||||
break;
|
break;
|
||||||
case 'add':
|
case 'add':
|
||||||
case 'delete':
|
case 'delete':
|
||||||
return next(new ldap.UnwillingToPerformError('only replace allowed'));
|
return next(new ldap.UnwillingToPerformError('only replace allowed'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var passwd = spawn('chpasswd', ['-c', 'MD5']);
|
const passwd = spawn('chpasswd', ['-c', 'MD5']);
|
||||||
passwd.stdin.end(user.cn + ':' + mod.vals[0], 'utf8');
|
passwd.stdin.end(user.cn + ':' + mod.vals[0], 'utf8');
|
||||||
|
|
||||||
passwd.on('exit', function(code) {
|
passwd.on('exit', (code) => {
|
||||||
if (code !== 0)
|
if (code !== 0)
|
||||||
return next(new ldap.OperationsError(code));
|
return next(new ldap.OperationsError(code));
|
||||||
|
|
||||||
res.end();
|
|
||||||
return next();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
Basically, we made sure the remote client was targeting an entry that exists,
|
Basically, we made sure the remote client was targeting an entry that exists,
|
||||||
ensuring that they were asking to "replace" the `userPassword` attribute (which
|
ensuring that they were asking to "replace" the `userPassword` attribute (which
|
||||||
|
@ -578,15 +626,19 @@ is the 'standard' LDAP attribute for passwords; if you think it's easier to use
|
||||||
command (which lets you change a user's password over stdin). Next, go ahead
|
command (which lets you change a user's password over stdin). Next, go ahead
|
||||||
and create a `passwd.ldif` file:
|
and create a `passwd.ldif` file:
|
||||||
|
|
||||||
dn: cn=ldapjs, ou=users, o=myhost
|
```shell
|
||||||
changetype: modify
|
dn: cn=ldapjs, ou=users, o=myhost
|
||||||
replace: userPassword
|
changetype: modify
|
||||||
userPassword: secret
|
replace: userPassword
|
||||||
-
|
userPassword: secret
|
||||||
|
-
|
||||||
|
```
|
||||||
|
|
||||||
And then run the OpenLDAP CLI:
|
And then run the OpenLDAP CLI:
|
||||||
|
|
||||||
$ ldapmodify -H ldap://localhost:1389 -x -D cn=root -w secret -f ./passwd.ldif
|
```shell
|
||||||
|
$ ldapmodify -H ldap://localhost:1389 -x -D cn=root -w secret -f ./passwd.ldif
|
||||||
|
```
|
||||||
|
|
||||||
You should now be able to login to your box as the ldapjs user. Let's get
|
You should now be able to login to your box as the ldapjs user. Let's get
|
||||||
the last "mainline" piece of work out of the way, and delete the user.
|
the last "mainline" piece of work out of the way, and delete the user.
|
||||||
|
@ -596,37 +648,40 @@ the last "mainline" piece of work out of the way, and delete the user.
|
||||||
Delete is pretty straightforward. The client gives you a dn to delete, and you
|
Delete is pretty straightforward. The client gives you a dn to delete, and you
|
||||||
delete it :). Add the following code into your server:
|
delete it :). Add the following code into your server:
|
||||||
|
|
||||||
server.del('ou=users, o=myhost', pre, function(req, res, next) {
|
```js
|
||||||
if (!req.dn.rdns[0].attrs.cn || !req.users[req.dn.rdns[0].attrs.cn.value])
|
server.del('ou=users, o=myhost', pre, (req, res, next) => {
|
||||||
return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
if (!req.dn.rdns[0].attrs.cn || !req.users[req.dn.rdns[0].attrs.cn.value])
|
||||||
|
return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||||
|
|
||||||
var userdel = spawn('userdel', ['-f', req.dn.rdns[0].attrs.cn.value]);
|
const userdel = spawn('userdel', ['-f', req.dn.rdns[0].attrs.cn.value]);
|
||||||
|
|
||||||
var messages = [];
|
const messages = [];
|
||||||
userdel.stdout.on('data', function(data) {
|
userdel.stdout.on('data', (data) => {
|
||||||
messages.push(data.toString());
|
messages.push(data.toString());
|
||||||
});
|
});
|
||||||
userdel.stderr.on('data', function(data) {
|
userdel.stderr.on('data', (data) => {
|
||||||
messages.push(data.toString());
|
messages.push(data.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
userdel.on('exit', function(code) {
|
userdel.on('exit', (code) => {
|
||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
var msg = '' + code;
|
let msg = '' + code;
|
||||||
if (messages.length)
|
if (messages.length)
|
||||||
msg += ': ' + messages.join();
|
msg += ': ' + messages.join();
|
||||||
return next(new ldap.OperationsError(msg));
|
return next(new ldap.OperationsError(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
res.end();
|
res.end();
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
```
|
||||||
|
|
||||||
And then run the following command:
|
And then run the following command:
|
||||||
|
|
||||||
$ ldapdelete -H ldap://localhost:1389 -x -D cn=root -w secret "cn=ldapjs, ou=users, o=myhost"
|
```shell
|
||||||
|
$ ldapdelete -H ldap://localhost:1389 -x -D cn=root -w secret "cn=ldapjs, ou=users, o=myhost"
|
||||||
|
```
|
||||||
|
|
||||||
# Where to go from here
|
# Where to go from here
|
||||||
|
|
||||||
|
|
|
@ -17,32 +17,36 @@ with HTTP services in node and [restify](http://restify.com).
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
var ldap = require('ldapjs');
|
```js
|
||||||
|
const ldap = require('ldapjs');
|
||||||
|
|
||||||
var server = ldap.createServer();
|
const server = ldap.createServer();
|
||||||
|
|
||||||
server.search('o=example', function(req, res, next) {
|
server.search('o=example', (req, res, next) => {
|
||||||
var obj = {
|
const obj = {
|
||||||
dn: req.dn.toString(),
|
dn: req.dn.toString(),
|
||||||
attributes: {
|
attributes: {
|
||||||
objectclass: ['organization', 'top'],
|
objectclass: ['organization', 'top'],
|
||||||
o: 'example'
|
o: 'example'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (req.filter.matches(obj.attributes))
|
if (req.filter.matches(obj.attributes))
|
||||||
res.send(obj);
|
res.send(obj);
|
||||||
|
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
server.listen(1389, function() {
|
server.listen(1389, () => {
|
||||||
console.log('LDAP server listening at %s', server.url);
|
console.log('LDAP server listening at %s', server.url);
|
||||||
});
|
});
|
||||||
|
```
|
||||||
|
|
||||||
Try hitting that with:
|
Try hitting that with:
|
||||||
|
|
||||||
$ ldapsearch -H ldap://localhost:1389 -x -b o=example objectclass=*
|
```shell
|
||||||
|
$ ldapsearch -H ldap://localhost:1389 -x -b o=example objectclass=*
|
||||||
|
```
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
|
@ -55,7 +59,9 @@ that you can build LDAP over anything you want, not just traditional databases.
|
||||||
|
|
||||||
# Getting started
|
# Getting started
|
||||||
|
|
||||||
$ npm install ldapjs
|
```shell
|
||||||
|
$ npm install ldapjs
|
||||||
|
```
|
||||||
|
|
||||||
If you're new to LDAP, check out the [guide](guide.html). Otherwise, the
|
If you're new to LDAP, check out the [guide](guide.html). Otherwise, the
|
||||||
API documentation is:
|
API documentation is:
|
||||||
|
|
251
docs/server.md
251
docs/server.md
|
@ -15,7 +15,9 @@ with LDAP. If you're not, read the [guide](guide.html) first.
|
||||||
|
|
||||||
The code to create a new server looks like:
|
The code to create a new server looks like:
|
||||||
|
|
||||||
var server = ldap.createServer();
|
```js
|
||||||
|
const server = ldap.createServer();
|
||||||
|
```
|
||||||
|
|
||||||
The full list of options is:
|
The full list of options is:
|
||||||
|
|
||||||
|
@ -68,10 +70,11 @@ available.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
server.listen(389, '127.0.0.1', function() {
|
```js
|
||||||
console.log('LDAP server listening at: ' + server.url);
|
server.listen(389, '127.0.0.1', function() {
|
||||||
});
|
console.log('LDAP server listening at: ' + server.url);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### Port and Host
|
### Port and Host
|
||||||
`listen(port, [host], [callback])`
|
`listen(port, [host], [callback])`
|
||||||
|
@ -115,14 +118,16 @@ paradigm of programming. Essentially every method is of the form
|
||||||
handlers together by calling `next()` and ordering your functions in the
|
handlers together by calling `next()` and ordering your functions in the
|
||||||
definition of the route. For example:
|
definition of the route. For example:
|
||||||
|
|
||||||
function authorize(req, res, next) {
|
```js
|
||||||
if (!req.connection.ldap.bindDN.equals('cn=root'))
|
function authorize(req, res, next) {
|
||||||
return next(new ldap.InsufficientAccessRightsError());
|
if (!req.connection.ldap.bindDN.equals('cn=root'))
|
||||||
|
return next(new ldap.InsufficientAccessRightsError());
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
server.search('o=example', authorize, function(req, res, next) { ... });
|
server.search('o=example', authorize, function(req, res, next) { ... });
|
||||||
|
```
|
||||||
|
|
||||||
Note that ldapjs is also slightly different, since it's often going to be backed
|
Note that ldapjs is also slightly different, since it's often going to be backed
|
||||||
to a DB-like entity, in that it also has an API where you can pass in a
|
to a DB-like entity, in that it also has an API where you can pass in a
|
||||||
|
@ -134,23 +139,25 @@ complete implementation of the LDAP protocol over
|
||||||
[Riak](https://github.com/basho/riak). Getting an LDAP server up with riak
|
[Riak](https://github.com/basho/riak). Getting an LDAP server up with riak
|
||||||
looks like:
|
looks like:
|
||||||
|
|
||||||
var ldap = require('ldapjs');
|
```js
|
||||||
var ldapRiak = require('ldapjs-riak');
|
const ldap = require('ldapjs');
|
||||||
|
const ldapRiak = require('ldapjs-riak');
|
||||||
|
|
||||||
var server = ldap.createServer();
|
const server = ldap.createServer();
|
||||||
var backend = ldapRiak.createBackend({
|
const backend = ldapRiak.createBackend({
|
||||||
"host": "localhost",
|
"host": "localhost",
|
||||||
"port": 8098,
|
"port": 8098,
|
||||||
"bucket": "example",
|
"bucket": "example",
|
||||||
"indexes": ["l", "cn"],
|
"indexes": ["l", "cn"],
|
||||||
"uniqueIndexes": ["uid"],
|
"uniqueIndexes": ["uid"],
|
||||||
"numConnections": 5
|
"numConnections": 5
|
||||||
});
|
});
|
||||||
|
|
||||||
server.add("o=example",
|
server.add("o=example",
|
||||||
backend,
|
backend,
|
||||||
backend.add());
|
backend.add());
|
||||||
...
|
...
|
||||||
|
```
|
||||||
|
|
||||||
The first parameter to an ldapjs route is always the point in the
|
The first parameter to an ldapjs route is always the point in the
|
||||||
tree to mount the handler chain at. The second argument is _optionally_ a
|
tree to mount the handler chain at. The second argument is _optionally_ a
|
||||||
|
@ -163,10 +170,12 @@ operation requires specific methods/fields on the request/response
|
||||||
objects. However, there is a `.use()` method availabe, similar to
|
objects. However, there is a `.use()` method availabe, similar to
|
||||||
that on express/connect, allowing you to chain up "middleware":
|
that on express/connect, allowing you to chain up "middleware":
|
||||||
|
|
||||||
server.use(function(req, res, next) {
|
```js
|
||||||
console.log('hello world');
|
server.use(function(req, res, next) {
|
||||||
return next();
|
console.log('hello world');
|
||||||
});
|
return next();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## Common Request Elements
|
## Common Request Elements
|
||||||
|
|
||||||
|
@ -210,11 +219,13 @@ the paradigm is something defined like CONSTRAINT\_VIOLATION in the RFC would be
|
||||||
`ConstraintViolationError` in ldapjs. Upon calling `next(new LDAPError())`,
|
`ConstraintViolationError` in ldapjs. Upon calling `next(new LDAPError())`,
|
||||||
ldapjs will _stop_ calling your handler chain. For example:
|
ldapjs will _stop_ calling your handler chain. For example:
|
||||||
|
|
||||||
server.search('o=example',
|
```js
|
||||||
function(req, res, next) { return next(); },
|
server.search('o=example',
|
||||||
function(req, res, next) { return next(new ldap.OperationsError()); },
|
(req, res, next) => { return next(); },
|
||||||
function(req, res, next) { res.end(); }
|
(req, res, next) => { return next(new ldap.OperationsError()); },
|
||||||
);
|
(req, res, next) => { res.end(); }
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
In the code snipped above, the third handler would never get invoked.
|
In the code snipped above, the third handler would never get invoked.
|
||||||
|
|
||||||
|
@ -222,11 +233,13 @@ In the code snipped above, the third handler would never get invoked.
|
||||||
|
|
||||||
Adds a mount in the tree to perform LDAP binds with. Example:
|
Adds a mount in the tree to perform LDAP binds with. Example:
|
||||||
|
|
||||||
server.bind('ou=people, o=example', function(req, res, next) {
|
```js
|
||||||
console.log('bind DN: ' + req.dn.toString());
|
server.bind('ou=people, o=example', (req, res, next) => {
|
||||||
console.log('bind PW: ' + req.credentials);
|
console.log('bind DN: ' + req.dn.toString());
|
||||||
res.end();
|
console.log('bind PW: ' + req.credentials);
|
||||||
});
|
res.end();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## BindRequest
|
## BindRequest
|
||||||
|
|
||||||
|
@ -259,11 +272,13 @@ No extra methods above an `LDAPResult` API call.
|
||||||
|
|
||||||
Adds a mount in the tree to perform LDAP adds with.
|
Adds a mount in the tree to perform LDAP adds with.
|
||||||
|
|
||||||
server.add('ou=people, o=example', function(req, res, next) {
|
```js
|
||||||
console.log('DN: ' + req.dn.toString());
|
server.add('ou=people, o=example', (req, res, next) => {
|
||||||
console.log('Entry attributes: ' + req.toObject().attributes);
|
console.log('DN: ' + req.dn.toString());
|
||||||
res.end();
|
console.log('Entry attributes: ' + req.toObject().attributes);
|
||||||
});
|
res.end();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## AddRequest
|
## AddRequest
|
||||||
|
|
||||||
|
@ -287,14 +302,16 @@ a standard JavaScript object.
|
||||||
This operation will return a plain JavaScript object from the request that looks
|
This operation will return a plain JavaScript object from the request that looks
|
||||||
like:
|
like:
|
||||||
|
|
||||||
{
|
```js
|
||||||
dn: 'cn=foo, o=example', // string, not DN object
|
{
|
||||||
attributes: {
|
dn: 'cn=foo, o=example', // string, not DN object
|
||||||
cn: ['foo'],
|
attributes: {
|
||||||
sn: ['bar'],
|
cn: ['foo'],
|
||||||
objectclass: ['person', 'top']
|
sn: ['bar'],
|
||||||
}
|
objectclass: ['person', 'top']
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## AddResponse
|
## AddResponse
|
||||||
|
|
||||||
|
@ -304,12 +321,14 @@ No extra methods above an `LDAPResult` API call.
|
||||||
|
|
||||||
Adds a handler for the LDAP search operation.
|
Adds a handler for the LDAP search operation.
|
||||||
|
|
||||||
server.search('o=example', function(req, res, next) {
|
```js
|
||||||
console.log('base object: ' + req.dn.toString());
|
server.search('o=example', (req, res, next) => {
|
||||||
console.log('scope: ' + req.scope);
|
console.log('base object: ' + req.dn.toString());
|
||||||
console.log('filter: ' + req.filter.toString());
|
console.log('scope: ' + req.scope);
|
||||||
res.end();
|
console.log('filter: ' + req.filter.toString());
|
||||||
});
|
res.end();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## SearchRequest
|
## SearchRequest
|
||||||
|
|
||||||
|
@ -367,34 +386,38 @@ explicitly pass in a `SearchEntry` object, and can instead just send a plain
|
||||||
JavaScript object that matches the format used from `AddRequest.toObject()`.
|
JavaScript object that matches the format used from `AddRequest.toObject()`.
|
||||||
|
|
||||||
|
|
||||||
server.search('o=example', function(req, res, next) {
|
```js
|
||||||
var obj = {
|
server.search('o=example', (req, res, next) => {
|
||||||
dn: 'o=example',
|
const obj = {
|
||||||
attributes: {
|
dn: 'o=example',
|
||||||
objectclass: ['top', 'organization'],
|
attributes: {
|
||||||
o: ['example']
|
objectclass: ['top', 'organization'],
|
||||||
}
|
o: ['example']
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (req.filter.matches(obj))
|
if (req.filter.matches(obj))
|
||||||
res.send(obj)
|
res.send(obj)
|
||||||
|
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
```
|
||||||
|
|
||||||
# modify
|
# modify
|
||||||
|
|
||||||
Allows you to handle an LDAP modify operation.
|
Allows you to handle an LDAP modify operation.
|
||||||
|
|
||||||
server.modify('o=example', function(req, res, next) {
|
```js
|
||||||
console.log('DN: ' + req.dn.toString());
|
server.modify('o=example', (req, res, next) => {
|
||||||
console.log('changes:');
|
console.log('DN: ' + req.dn.toString());
|
||||||
req.changes.forEach(function(c) {
|
console.log('changes:');
|
||||||
console.log(' operation: ' + c.operation);
|
for (const c of req.changes) {
|
||||||
console.log(' modification: ' + c.modification.toString());
|
console.log(' operation: ' + c.operation);
|
||||||
});
|
console.log(' modification: ' + c.modification.toString());
|
||||||
res.end();
|
}
|
||||||
});
|
res.end();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## ModifyRequest
|
## ModifyRequest
|
||||||
|
|
||||||
|
@ -431,10 +454,12 @@ No extra methods above an `LDAPResult` API call.
|
||||||
|
|
||||||
Allows you to handle an LDAP delete operation.
|
Allows you to handle an LDAP delete operation.
|
||||||
|
|
||||||
server.del('o=example', function(req, res, next) {
|
```js
|
||||||
console.log('DN: ' + req.dn.toString());
|
server.del('o=example', (req, res, next) => {
|
||||||
res.end();
|
console.log('DN: ' + req.dn.toString());
|
||||||
});
|
res.end();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## DeleteRequest
|
## DeleteRequest
|
||||||
|
|
||||||
|
@ -451,12 +476,14 @@ No extra methods above an `LDAPResult` API call.
|
||||||
|
|
||||||
Allows you to handle an LDAP compare operation.
|
Allows you to handle an LDAP compare operation.
|
||||||
|
|
||||||
server.compare('o=example', function(req, res, next) {
|
```js
|
||||||
console.log('DN: ' + req.dn.toString());
|
server.compare('o=example', (req, res, next) => {
|
||||||
console.log('attribute name: ' + req.attribute);
|
console.log('DN: ' + req.dn.toString());
|
||||||
console.log('attribute value: ' + req.value);
|
console.log('attribute name: ' + req.attribute);
|
||||||
res.end(req.value === 'foo');
|
console.log('attribute value: ' + req.value);
|
||||||
});
|
res.end(req.value === 'foo');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## CompareRequest
|
## CompareRequest
|
||||||
|
|
||||||
|
@ -483,15 +510,17 @@ that, there are no extra methods above an `LDAPResult` API call.
|
||||||
|
|
||||||
Allows you to handle an LDAP modifyDN operation.
|
Allows you to handle an LDAP modifyDN operation.
|
||||||
|
|
||||||
server.modifyDN('o=example', function(req, res, next) {
|
```js
|
||||||
console.log('DN: ' + req.dn.toString());
|
server.modifyDN('o=example', (req, res, next) => {
|
||||||
console.log('new RDN: ' + req.newRdn.toString());
|
console.log('DN: ' + req.dn.toString());
|
||||||
console.log('deleteOldRDN: ' + req.deleteOldRdn);
|
console.log('new RDN: ' + req.newRdn.toString());
|
||||||
console.log('new superior: ' +
|
console.log('deleteOldRDN: ' + req.deleteOldRdn);
|
||||||
(req.newSuperior ? req.newSuperior.toString() : ''));
|
console.log('new superior: ' +
|
||||||
|
(req.newSuperior ? req.newSuperior.toString() : ''));
|
||||||
|
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## ModifyDNRequest
|
## ModifyDNRequest
|
||||||
|
|
||||||
|
@ -525,14 +554,16 @@ OID, but ldapjs makes no such restrictions; it just needs to be a string.
|
||||||
Unlike the other operations, extended operations don't map to any location in
|
Unlike the other operations, extended operations don't map to any location in
|
||||||
the tree, so routing here will be exact match, as opposed to subtree.
|
the tree, so routing here will be exact match, as opposed to subtree.
|
||||||
|
|
||||||
// LDAP whoami
|
```js
|
||||||
server.exop('1.3.6.1.4.1.4203.1.11.3', function(req, res, next) {
|
// LDAP whoami
|
||||||
console.log('name: ' + req.name);
|
server.exop('1.3.6.1.4.1.4203.1.11.3', (req, res, next) => {
|
||||||
console.log('value: ' + req.value);
|
console.log('name: ' + req.name);
|
||||||
res.value = 'u:xxyyz@EXAMPLE.NET';
|
console.log('value: ' + req.value);
|
||||||
res.end();
|
res.value = 'u:xxyyz@EXAMPLE.NET';
|
||||||
return next();
|
res.end();
|
||||||
});
|
return next();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## ExtendedRequest
|
## ExtendedRequest
|
||||||
|
|
||||||
|
@ -563,9 +594,11 @@ and cleans up any internals (in ldapjs core). You can override this handler
|
||||||
if you need to clean up any items in your backend, or perform any other cleanup
|
if you need to clean up any items in your backend, or perform any other cleanup
|
||||||
tasks you need to.
|
tasks you need to.
|
||||||
|
|
||||||
server.unbind(function(req, res, next) {
|
```js
|
||||||
res.end();
|
server.unbind((req, res, next) => {
|
||||||
});
|
res.end();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
Note that the LDAP unbind operation actually doesn't send any response (by
|
Note that the LDAP unbind operation actually doesn't send any response (by
|
||||||
definition in the RFC), so the UnbindResponse is really just a stub that
|
definition in the RFC), so the UnbindResponse is really just a stub that
|
||||||
|
|
|
@ -27,13 +27,14 @@
|
||||||
"verror": "^1.8.1"
|
"verror": "^1.8.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^7.14.0",
|
"eslint": "^7.20.0",
|
||||||
"eslint-config-standard": "^16.0.2",
|
"eslint-config-standard": "^16.0.2",
|
||||||
"eslint-plugin-import": "^2.22.1",
|
"eslint-plugin-import": "^2.22.1",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^4.3.1",
|
||||||
"front-matter": "^4.0.2",
|
"front-matter": "^4.0.2",
|
||||||
"get-port": "^5.1.1",
|
"get-port": "^5.1.1",
|
||||||
|
"highlight.js": "^10.6.0",
|
||||||
"husky": "^4.2.5",
|
"husky": "^4.2.5",
|
||||||
"marked": "^2.0.0",
|
"marked": "^2.0.0",
|
||||||
"tap": "14.11.0"
|
"tap": "14.11.0"
|
||||||
|
|
|
@ -2,6 +2,17 @@ const fs = require('fs/promises')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const marked = require('marked')
|
const marked = require('marked')
|
||||||
const fm = require('front-matter')
|
const fm = require('front-matter')
|
||||||
|
const { highlight } = require('highlight.js')
|
||||||
|
|
||||||
|
marked.use({
|
||||||
|
highlight: (code, lang) => {
|
||||||
|
if (lang) {
|
||||||
|
return highlight(lang, code).value
|
||||||
|
}
|
||||||
|
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function tocHTML (toc) {
|
function tocHTML (toc) {
|
||||||
let html = '<ul>\n'
|
let html = '<ul>\n'
|
||||||
|
@ -92,10 +103,14 @@ async function createDocs () {
|
||||||
|
|
||||||
const dest = path.join(dist, 'media')
|
const dest = path.join(dist, 'media')
|
||||||
const src = path.join(branding, 'media')
|
const src = path.join(branding, 'media')
|
||||||
|
const highlightjsStyles = path.resolve(__dirname, '..', 'node_modules', 'highlight.js', 'styles')
|
||||||
await fs.mkdir(dest)
|
await fs.mkdir(dest)
|
||||||
await fs.mkdir(path.join(dest, 'css'))
|
await fs.mkdir(path.join(dest, 'css'))
|
||||||
|
await fs.mkdir(path.join(dest, 'js'))
|
||||||
await fs.mkdir(path.join(dest, 'img'))
|
await fs.mkdir(path.join(dest, 'img'))
|
||||||
await fs.copyFile(path.join(src, 'css', 'style.css'), path.join(dest, 'css', 'style.css'))
|
await fs.copyFile(path.join(src, 'css', 'style.css'), path.join(dest, 'css', 'style.css'))
|
||||||
|
await fs.copyFile(path.join(highlightjsStyles, 'default.css'), path.join(dest, 'css', 'highlight.css'))
|
||||||
|
await fs.copyFile(path.join(src, 'js', 'script.js'), path.join(dest, 'js', 'script.js'))
|
||||||
await fs.copyFile(path.join(src, 'img', 'logo.svg'), path.join(dest, 'img', 'logo.svg'))
|
await fs.copyFile(path.join(src, 'img', 'logo.svg'), path.join(dest, 'img', 'logo.svg'))
|
||||||
await fs.copyFile(path.join(branding, 'CNAME'), path.join(dist, 'CNAME'))
|
await fs.copyFile(path.join(branding, 'CNAME'), path.join(dist, 'CNAME'))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue