Prompt for nickname on registration (#4)

Allow users to set a nickname while registering, controlled by settings. Also, add a setting to hide the username input entirely and randomly generate the username.

Co-authored-by: Sami Mazouz <sychocouldy@gmail.com>
This commit is contained in:
Alexander Skvortsov 2021-11-17 11:17:36 -05:00 committed by GitHub
parent 745de66d23
commit 3c5229610f
20 changed files with 4172 additions and 2189 deletions

View File

@ -0,0 +1,78 @@
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
php: [7.3, 7.4, '8.0']
service: ['mysql:5.7', mariadb]
prefix: ['', flarum_]
include:
- service: 'mysql:5.7'
db: MySQL
- service: mariadb
db: MariaDB
- prefix: flarum_
prefixStr: (prefix)
exclude:
- php: 7.3
service: 'mysql:5.7'
prefix: flarum_
- php: 7.3
service: mariadb
prefix: flarum_
- php: 8.0
service: 'mysql:5.7'
prefix: flarum_
- php: 8.0
service: mariadb
prefix: flarum_
services:
mysql:
image: ${{ matrix.service }}
ports:
- 13306:3306
name: 'PHP ${{ matrix.php }} / ${{ matrix.db }} ${{ matrix.prefixStr }}'
steps:
- uses: actions/checkout@master
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: xdebug
extensions: curl, dom, gd, json, mbstring, openssl, pdo_mysql, tokenizer, zip
tools: phpunit, composer:v2
# The authentication alter is necessary because newer mysql versions use the `caching_sha2_password` driver,
# which isn't supported prior to PHP7.4
# When we drop support for PHP7.3, we should remove this from the setup.
- name: Create MySQL Database
run: |
sudo systemctl start mysql
mysql -uroot -proot -e 'CREATE DATABASE flarum_test;' --port 13306
mysql -uroot -proot -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root';" --port 13306
- name: Install Composer dependencies
run: composer install
- name: Setup Composer tests
run: composer test:setup
env:
DB_PORT: 13306
DB_PASSWORD: root
DB_PREFIX: ${{ matrix.prefix }}
- name: Run Composer tests
run: composer test
env:
COMPOSER_PROCESS_TIMEOUT: 600

View File

@ -4,3 +4,4 @@ composer.phar
Thumbs.db
node_modules
js/dist/*
*.cache

View File

@ -1,41 +1,56 @@
{
"name": "flarum/nicknames",
"description": "Allow users to set nicknames.",
"type": "flarum-extension",
"keywords": ["nicknames"],
"license": "MIT",
"support": {
"issues": "https://github.com/flarum/core/issues",
"source": "https://github.com/flarum/nicknames",
"forum": "https://discuss.flarum.org"
},
"homepage": "https://flarum.org",
"funding": [
{
"type": "website",
"url": "https://flarum.org/donate/"
}
],
"require": {
"flarum/core": "^1.1"
},
"autoload": {
"psr-4": {
"Flarum\\Nicknames\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
},
"flarum-extension": {
"title": "Nicknames",
"category": "feature",
"icon": {
"name": "fas fa-user-tag",
"backgroundColor": "#8E4529",
"color": "#ffffff"
}
}
"name": "flarum/nicknames",
"description": "Allow users to set nicknames.",
"type": "flarum-extension",
"keywords": [
"nicknames"
],
"license": "MIT",
"support": {
"issues": "https://github.com/flarum/core/issues",
"source": "https://github.com/flarum/nicknames",
"forum": "https://discuss.flarum.org"
},
"homepage": "https://flarum.org",
"funding": [
{
"type": "website",
"url": "https://flarum.org/donate/"
}
],
"require": {
"flarum/core": "^1.1"
},
"autoload": {
"psr-4": {
"Flarum\\Nicknames\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
},
"flarum-extension": {
"title": "Nicknames",
"category": "feature",
"icon": {
"name": "fas fa-user-tag",
"backgroundColor": "#8E4529",
"color": "#ffffff"
}
}
},
"scripts": {
"test": [
"@test:unit",
"@test:integration"
],
"test:unit": "phpunit -c tests/phpunit.unit.xml",
"test:integration": "phpunit -c tests/phpunit.integration.xml",
"test:setup": "@php tests/integration/setup.php"
},
"require-dev": {
"flarum/testing": "^1.0",
"flarum/core": "1.x-dev"
}
}

View File

@ -13,8 +13,10 @@ namespace Flarum\Nicknames;
use Flarum\Api\Serializer\UserSerializer;
use Flarum\Extend;
use Flarum\Nicknames\Access\UserPolicy;
use Flarum\User\Event\Saving;
use Flarum\User\Search\UserSearcher;
use Flarum\User\User;
use Flarum\User\UserValidator;
return [
@ -39,11 +41,19 @@ return [
}),
(new Extend\Settings())
->serializeToForum('displayNameDriver', 'display_name_driver', null, 'username'),
->default('flarum-nicknames.set_on_registration', true)
->default('flarum-nicknames.min', 1)
->default('flarum-nicknames.max', 150)
->serializeToForum('displayNameDriver', 'display_name_driver', null, 'username')
->serializeToForum('setNicknameOnRegistration', 'flarum-nicknames.set_on_registration', 'boolval')
->serializeToForum('randomizeUsernameOnRegistration', 'flarum-nicknames.random_username', 'boolval'),
(new Extend\Validator(UserValidator::class))
->configure(AddNicknameValidation::class),
(new Extend\SimpleFlarumSearch(UserSearcher::class))
->setFullTextGambit(NicknameFullTextGambit::class)
->setFullTextGambit(NicknameFullTextGambit::class),
(new Extend\Policy())
->modelPolicy(User::class, UserPolicy::class),
];

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,17 @@ app.initializers.add('flarum/nicknames', () => {
</div>
);
})
.registerSetting({
setting: 'flarum-nicknames.set_on_registration',
type: 'boolean',
label: app.translator.trans('flarum-nicknames.admin.settings.set_on_registration_label')
})
.registerSetting({
setting: 'flarum-nicknames.random_username',
type: 'boolean',
label: app.translator.trans('flarum-nicknames.admin.settings.random_username_label'),
help: app.translator.trans('flarum-nicknames.admin.settings.random_username_help')
})
.registerSetting({
setting: 'flarum-nicknames.unique',
type: 'boolean',

View File

@ -1,6 +1,7 @@
import { extend } from 'flarum/common/extend';
import Button from 'flarum/common/components/Button';
import EditUserModal from 'flarum/common/components/EditUserModal';
import SignUpModal from 'flarum/forum/components/SignUpModal';
import SettingsPage from 'flarum/forum/components/SettingsPage';
import Model from 'flarum/common/Model';
import User from 'flarum/common/models/User';
@ -50,5 +51,60 @@ app.initializers.add('flarum/nicknames', () => {
}
});
extend(SignUpModal.prototype, 'oninit', function () {
if (app.forum.attribute('displayNameDriver') !== 'nickname') return;
this.nickname = Stream(this.attrs.username || '');
});
extend(SignUpModal.prototype, 'onready', function () {
if (app.forum.attribute('displayNameDriver') !== 'nickname') return;
if (app.forum.attribute('setNicknameOnRegistration') && app.forum.attribute('randomizeUsernameOnRegistration')) {
this.$('[name=nickname]').select();
}
});
extend(SignUpModal.prototype, 'fields', function (items) {
if (app.forum.attribute('displayNameDriver') !== 'nickname') return;
if (app.forum.attribute('setNicknameOnRegistration')) {
items.add(
'nickname',
<div className="Form-group">
<input
className="FormControl"
name="nickname"
type="text"
placeholder={extractText(app.translator.trans('flarum-nicknames.forum.sign_up.nickname_placeholder'))}
bidi={this.nickname}
disabled={this.loading || this.isProvided('nickname')}
required={app.forum.attribute('randomizeUsernameOnRegistration')}
/>
</div>,
25
);
if (app.forum.attribute('randomizeUsernameOnRegistration')) {
items.remove('username');
}
}
});
extend(SignUpModal.prototype, 'submitData', function (data) {
if (app.forum.attribute('displayNameDriver') !== 'nickname') return;
if (app.forum.attribute('setNicknameOnRegistration')) {
data.nickname = this.nickname();
if (app.forum.attribute('randomizeUsernameOnRegistration')) {
const arr = new Uint32Array(2);
crypto.getRandomValues(arr);
data.username = arr.join('');
}
}
});
});

View File

@ -5,8 +5,11 @@ flarum-nicknames:
settings:
max_label: Maximum nickname length
min_label: Minimum nickname length
random_username_label: Randomize Usernames
random_username_help: This will hide the `username` input on registration, and use a random number instead. It will also make the `nickname` field mandatory. This will only take effect if "Allow setting nicknames on registration" is enabled.
regex_label: Regular expression for validation
unique_label: Require unique nicknames?
set_on_registration_label: Allow setting nicknames on registration
unique_label: Require unique nicknames
wrong_driver: You must select "nickname" as the display name driver on the <a><strong>Basics Page</strong></a> for this extension to take effect.
api:
invalid_nickname_message: This nickname is invalid. Please contact your forum administrator for more information on nickname rules.
@ -16,9 +19,11 @@ flarum-nicknames:
title: => flarum-nicknames.ref.change_nickname
edit_user:
nicknames_heading: => flarum-nicknames.ref.change_nickname
nicknames_text: Nickname
nicknames_text: => flarum-nicknames.ref.nickname
settings:
change_nickname_button: => flarum-nicknames.ref.change_nickname
sign_up:
nickname_placeholder: => flarum-nicknames.ref.nickname
ref:
change_nickname: Change Nickname
nickname: Nickname

View File

@ -7,7 +7,7 @@ return [
'up' => function(Builder $schema) {
if (!$schema->hasColumn('users', 'nickname')) {
$schema->table('users', function (Blueprint $table) use ($schema) {
$table->string('nickname', 150)->after('username')->index();
$table->string('nickname', 150)->after('username')->index()->nullable();
});
}
},

View File

@ -0,0 +1,14 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
$schema->table('users', function (Blueprint $table) {
$table->string('nickname')->nullable()->change();
});
},
'down' => function (Builder $schema) {
}
];

View File

@ -0,0 +1,43 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Nicknames\Access;
use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\Access\AbstractPolicy;
use Flarum\User\User;
class UserPolicy extends AbstractPolicy
{
/**
* @var SettingsRepositoryInterface
*/
protected $settings;
public function __construct(SettingsRepositoryInterface $settings)
{
$this->settings = $settings;
}
/**
* @param User $actor
* @param User $user
* @return bool|null
*/
public function editNickname(User $actor, User $user)
{
if ($actor->isGuest() && !$user->exists && $this->settings->get('flarum-nicknames.set_on_registration')) {
return $this->allow();
} else if ($actor->id === $user->id && $actor->hasPermission('user.editOwnNickname')) {
return $this->allow();
} else if ($actor->can('edit', $user)) {
return $this->allow();
}
}
}

View File

@ -5,6 +5,7 @@ namespace Flarum\Nicknames;
use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Validation\Validator;
use Symfony\Contracts\Translation\TranslatorInterface;
class AddNicknameValidation
{
@ -13,10 +14,16 @@ class AddNicknameValidation
*/
protected $settings;
/**
* @var TranslatorInterface
*/
protected $translator;
public function __construct(SettingsRepositoryInterface $settings)
public function __construct(SettingsRepositoryInterface $settings, TranslatorInterface $translator)
{
$this->settings = $settings;
$this->translator = $translator;
}
public function __invoke($flarumValidator, Validator $validator)
@ -28,7 +35,7 @@ class AddNicknameValidation
function ($attribute, $value, $fail) {
$regex = $this->settings->get('flarum-nicknames.regex');
if ($regex && !preg_match_all("/$regex/", $value)) {
$fail(app('translator')->trans('flarum-nicknames.api.invalid_nickname_message'));
$this->translator->trans('flarum-nicknames.api.invalid_nickname_message');
}
},
'min:' . $this->settings->get('flarum-nicknames.min'),

View File

@ -2,25 +2,30 @@
namespace Flarum\Nicknames;
use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\Event\Saving;
use Flarum\User\Exception\PermissionDeniedException;
use Illuminate\Support\Arr;
class SaveNicknameToDatabase {
/**
* @var SettingsRepositoryInterface
*/
protected $settings;
public function __construct(SettingsRepositoryInterface $settings) {
$this->settings = $settings;
}
public function handle(Saving $event)
{
$user = $event->user;
$data = $event->data;
$actor = $event->actor;
$isSelf = $actor->id === $user->id;
$attributes = Arr::get($data, 'attributes', []);
if (isset($attributes['nickname'])) {
if ($isSelf) {
$actor->assertCan('editOwnNickname', $user);
} else {
$actor->assertCan('edit', $user);
}
$actor->assertCan('editNickname', $user);
$nickname = $attributes['nickname'];

View File

View File

@ -0,0 +1,87 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Nicknames\Tests\integration;
use Flarum\Group\Group;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
use Flarum\User\User;
class UpdateTest extends TestCase
{
use RetrievesAuthorizedUsers;
/**
* @inheritDoc
*/
protected function setUp(): void
{
parent::setUp();
$this->extension('flarum-nicknames');
$this->prepareDatabase([
'users' => [
$this->normalUser(),
],
]);
}
/**
* @test
*/
public function user_cant_edit_own_nickname_if_not_allowed()
{
$this->database()->table('group_permission')->where('permission', 'user.editOwnNickname')->where('group_id', Group::MEMBER_ID)->delete();
$response = $this->send(
$this->request('PATCH', '/api/users/2', [
'authenticatedAs' => 2,
'json' => [
'data' => [
'attributes' => [
'nickname' => 'new nickname',
],
],
],
])
);
$this->assertEquals(403, $response->getStatusCode());
}
/**
* @test
*/
public function user_can_edit_own_nickname_if_allowed()
{
$this->prepareDatabase([
'group_permission' => [
['permission' => 'user.editOwnNickname', 'group_id' => 2],
]
]);
$response = $this->send(
$this->request('PATCH', '/api/users/2', [
'authenticatedAs' => 2,
'json' => [
'data' => [
'attributes' => [
'nickname' => 'new nickname',
],
],
],
])
);
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals('new nickname', User::find(2)->nickname);
}
}

View File

@ -0,0 +1,77 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Nicknames\Tests\integration;
use Flarum\Extend;
use Flarum\Testing\integration\TestCase;
use Flarum\User\User;
class RegisterTest extends TestCase
{
/**
* @inheritDoc
*/
protected function setUp(): void
{
$this->extension('flarum-nicknames');
$this->extend(
(new Extend\Csrf)->exemptRoute('register')
);
}
/**
* @test
*/
public function can_register_with_nickname()
{
$this->setting('flarum-nicknames.set_on_registration', true);
$response = $this->send(
$this->request('POST', '/register', [
'json' => [
'nickname' => 'фларум',
'username' => 'test',
'password' => 'too-obscure',
'email' => 'test@machine.local',
]
])
);
$this->assertEquals(201, $response->getStatusCode());
/** @var User $user */
$user = User::where('username', 'test')->firstOrFail();
$this->assertEquals(0, $user->is_email_confirmed);
$this->assertEquals('test', $user->username);
$this->assertEquals('test@machine.local', $user->email);
}
/**
* @test
*/
public function cant_register_with_nickname_if_not_allowed()
{
$this->setting('flarum-nicknames.set_on_registration', false);
$response = $this->send(
$this->request('POST', '/register', [
'json' => [
'nickname' => 'фларум',
'username' => 'test',
'password' => 'too-obscure',
'email' => 'test@machine.local',
]
])
);
$this->assertEquals(403, $response->getStatusCode());
}
}

View File

@ -7,9 +7,10 @@
* LICENSE file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Flarum\Testing\integration\Setup\SetupScript;
return Migration::addSettings([
'flarum-nicknames.max' => '150',
'flarum-nicknames.min' => '1',
]);
require __DIR__.'/../../vendor/autoload.php';
$setup = new SetupScript();
$setup->run();

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="true"
stopOnFailure="false"
>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">../src/</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Flarum Integration Tests">
<directory suffix="Test.php">./integration</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">../src/</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Flarum Unit Tests">
<directory suffix="Test.php">./unit</directory>
</testsuite>
</testsuites>
<listeners>
<listener class="\Mockery\Adapter\Phpunit\TestListener" />
</listeners>
</phpunit>

View File