Skip to content

Commit 93d7482

Browse files
authored
Merge pull request #893 from solid/feature/blacklist-username
A simple service for handling blacklisting of usernames
2 parents c827613 + db0d55b commit 93d7482

15 files changed

+187
-28
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ Note that this example creates the `fullchain.pem` and `privkey.pem` files
8282
in a directory one level higher from the current, so that you don't
8383
accidentally commit your certificates to `solid` while you're developing.
8484

85-
If you would like to get rid of the browser warnings, import your fullchain.pem certificate into your 'Trusted Root Certificate' store.
85+
If you would like to get rid of the browser warnings, import your fullchain.pem certificate into your 'Trusted Root Certificate' store.
8686

8787
### Run multi-user server (intermediate)
8888

@@ -375,6 +375,12 @@ In order to test a single component, you can run
375375
npm run test-(acl|formats|params|patch)
376376
```
377377

378+
## Blacklisted usernames
379+
380+
By default Solid will not allow [certain usernames as they might cause
381+
confusion or allow vulnerabilies for social engineering](https://github.com/marteinn/The-Big-Username-Blacklist).
382+
This list is configurable via `config/usernames-blacklist.json`. Solid does not
383+
blacklist profanities by default.
378384

379385
## Contributing
380386

config/usernames-blacklist.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"useTheBigUsernameBlacklist": true,
3+
"customBlacklistedUsernames": []
4+
}

lib/create-app.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ const authProxy = require('./handlers/auth-proxy')
1212
const SolidHost = require('./models/solid-host')
1313
const AccountManager = require('./models/account-manager')
1414
const vhost = require('vhost')
15-
const EmailService = require('./models/email-service')
16-
const TokenService = require('./models/token-service')
15+
const EmailService = require('./services/email-service')
16+
const TokenService = require('./services/token-service')
1717
const capabilityDiscovery = require('./capability-discovery')
1818
const API = require('./api')
1919
const errorPages = require('./handlers/error-pages')

lib/requests/create-account-request.js

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const AuthRequest = require('./auth-request')
44
const WebIdTlsCertificate = require('../models/webid-tls-certificate')
55
const debug = require('../debug').accounts
6+
const blacklistService = require('../services/blacklist-service')
67

78
/**
89
* Represents a 'create new user account' http request (either a POST to the
@@ -115,30 +116,28 @@ class CreateAccountRequest extends AuthRequest {
115116
/**
116117
* Creates an account for a given user (from a POST to `/api/accounts/new`)
117118
*
118-
* @throws {Error} An http 400 error if an account already exists
119+
* @throws {Error} If errors were encountering while validating the username.
119120
*
120121
* @return {Promise<UserAccount>} Resolves with newly created account instance
121122
*/
122-
createAccount () {
123+
async createAccount () {
123124
let userAccount = this.userAccount
124125
let accountManager = this.accountManager
125126

126-
return Promise.resolve(userAccount)
127-
.then(this.cancelIfUsernameInvalid.bind(this))
128-
.then(this.cancelIfAccountExists.bind(this))
129-
.then(this.createAccountStorage.bind(this))
130-
.then(this.saveCredentialsFor.bind(this))
131-
.then(this.sendResponse.bind(this))
132-
.then(userAccount => {
133-
// 'return' not used deliberately, no need to block and wait for email
134-
if (userAccount && userAccount.email) {
135-
debug('Sending Welcome email')
136-
accountManager.sendWelcomeEmail(userAccount)
137-
}
138-
})
139-
.then(() => {
140-
return userAccount
141-
})
127+
this.cancelIfUsernameInvalid(userAccount)
128+
this.cancelIfBlacklistedUsername(userAccount)
129+
await this.cancelIfAccountExists(userAccount)
130+
await this.createAccountStorage(userAccount)
131+
await this.saveCredentialsFor(userAccount)
132+
await this.sendResponse(userAccount)
133+
134+
// 'return' not used deliberately, no need to block and wait for email
135+
if (userAccount && userAccount.email) {
136+
debug('Sending Welcome email')
137+
accountManager.sendWelcomeEmail(userAccount)
138+
}
139+
140+
return userAccount
142141
}
143142

144143
/**
@@ -196,12 +195,33 @@ class CreateAccountRequest extends AuthRequest {
196195
* @throws {Error} If errors were encountering while validating the
197196
* username.
198197
*
199-
* @return {Promise<UserAccount>} Chainable
198+
* @return {UserAccount} Chainable
200199
*/
201200
cancelIfUsernameInvalid (userAccount) {
202201
if (!userAccount.username || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(userAccount.username)) {
203202
debug('Invalid username ' + userAccount.username)
204-
const error = new Error('Invalid username')
203+
const error = new Error('Invalid username (contains invalid characters)')
204+
error.status = 400
205+
throw error
206+
}
207+
208+
return userAccount
209+
}
210+
211+
/**
212+
* Check if a username is a valid slug.
213+
*
214+
* @param userAccount {UserAccount} Instance of the account to be created
215+
*
216+
* @throws {Error} If username is blacklisted
217+
*
218+
* @return {UserAccount} Chainable
219+
*/
220+
cancelIfBlacklistedUsername (userAccount) {
221+
const validUsername = blacklistService.validate(userAccount.username)
222+
if (!validUsername) {
223+
debug('Invalid username ' + userAccount.username)
224+
const error = new Error('Invalid username (username is blacklisted)')
205225
error.status = 400
206226
throw error
207227
}

lib/services/blacklist-service.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const blacklistConfig = require('../../config/usernames-blacklist.json')
2+
const blacklist = require('the-big-username-blacklist').list
3+
4+
class BlacklistService {
5+
constructor () {
6+
this.reset()
7+
}
8+
9+
addWord (word) {
10+
this.list.push(BlacklistService._prepareWord(word))
11+
}
12+
13+
reset (config) {
14+
this.list = BlacklistService._initList(config)
15+
}
16+
17+
validate (word) {
18+
return this.list.indexOf(BlacklistService._prepareWord(word)) === -1
19+
}
20+
21+
static _initList (config = blacklistConfig) {
22+
return [
23+
...(config.useTheBigUsernameBlacklist ? blacklist : []),
24+
...config.customBlacklistedUsernames
25+
]
26+
}
27+
28+
static _prepareWord (word) {
29+
return word.trim().toLocaleLowerCase()
30+
}
31+
}
32+
33+
module.exports = new BlacklistService()

lib/models/email-service.js renamed to lib/services/email-service.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const nodemailer = require('nodemailer')
44
const path = require('path')
5-
const debug = require('./../debug').email
5+
const debug = require('../debug').email
66

77
/**
88
* Models a Nodemailer-based email sending service.
File renamed without changes.

package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
"solid-permissions": "^0.6.0",
7878
"solid-ws": "^0.2.3",
7979
"text-encoder-lite": "^1.0.1",
80+
"the-big-username-blacklist": "^1.5.1",
8081
"ulid": "^0.1.0",
8182
"uuid": "^3.0.0",
8283
"valid-url": "^1.0.9",

test/unit/account-manager-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const LDP = require('../../lib/ldp')
1515
const SolidHost = require('../../lib/models/solid-host')
1616
const AccountManager = require('../../lib/models/account-manager')
1717
const UserAccount = require('../../lib/models/user-account')
18-
const TokenService = require('../../lib/models/token-service')
18+
const TokenService = require('../../lib/services/token-service')
1919
const WebIdTlsCertificate = require('../../lib/models/webid-tls-certificate')
2020

2121
const testAccountsDir = path.join(__dirname, '../resources/accounts')

0 commit comments

Comments
 (0)