OAuth (part 1 draft)

This commit is contained in:
Visman 2023-05-07 23:45:05 +07:00
parent 27cf2152c8
commit 8b72a4a859
19 changed files with 1102 additions and 2 deletions

View file

@ -94,6 +94,24 @@ class Routing
'Redirect:toIndex'
);
}
// OAuth
if (
$user->isAdmin
|| 1 === $config->b_oauth_allow
) {
$r->add(
$r::GET,
'/reglog/callback/{name}',
'RegLog:callback',
'RegLogCallback'
);
$r->add(
$r::PST,
'/reglog/redirect',
'RegLog:redirect',
'RegLogRedirect'
);
}
// просмотр разрешен
if (1 === $user->g_read_board) {
// главная
@ -557,6 +575,18 @@ class Routing
'AdminOptions:edit',
'AdminOptions'
);
$r->add(
$r::DUO,
'/admin/options/providers',
'AdminProviders:view',
'AdminProviders'
);
$r->add(
$r::DUO,
'/admin/options/providers/{name}',
'AdminProviders:edit',
'AdminProvider'
);
$r->add(
$r::DUO,
'/admin/parser',

View file

@ -110,6 +110,7 @@ class Options extends Admin
'i_poll_term' => 'required|integer|min:0|max:99',
'b_poll_guest' => 'required|integer|in:0,1',
'b_pm' => 'required|integer|in:0,1',
'b_oauth_allow' => 'required|integer|in:0,1',
])->addAliases([
])->addArguments([
])->addMessages([
@ -610,6 +611,19 @@ class Options extends Admin
'caption' => 'Report new label',
'help' => 'Report new help',
],
'b_oauth_allow' => [
'type' => 'radio',
'value' => $config->b_oauth_allow,
'values' => $yn,
'caption' => 'Allow oauth label',
'help' => 'Allow oauth help',
],
'configure_providers' => [
'type' => 'link',
'value' => __('Configure providers'),
'href' => $this->c->Router->link('AdminProviders'),
'title' => __('Configure providers'),
],
'b_rules' => [
'type' => 'radio',
'value' => $config->b_rules,

View file

@ -0,0 +1,234 @@
<?php
/**
* This file is part of the ForkBB <https://github.com/forkbb>.
*
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
* @license The MIT License (MIT)
*/
declare(strict_types=1);
namespace ForkBB\Models\Pages\Admin;
use ForkBB\Core\Container;
use ForkBB\Models\Page;
use ForkBB\Models\Pages\Admin;
use ForkBB\Models\Provider\Driver;
use function \ForkBB\__;
class Providers extends Admin
{
/**
* Выводит сообщение
*/
protected function mDisabled(): void
{
if (1 !== $this->c->config->b_oauth_allow) {
$this->fIswev = ['w', ['OAuth authorization disabled', $this->c->Router->link('AdminOptions', ['#' => 'id-fs-registration'])]];
}
}
/**
* Просмотр, редактирвоание и добавление категорий
*/
public function view(array $args, string $method): Page
{
$this->c->Lang->load('validator');
$this->c->Lang->load('admin_providers');
if ('POST' === $method) {
$v = $this->c->Validator->reset()
->addRules([
'token' => 'token:AdminProviders',
'form.*.pr_pos' => 'required|integer|min:0|max:9999999999',
'form.*.pr_allow' => 'checkbox',
])->addAliases([
])->addArguments([
])->addMessages([
]);
if ($v->validation($_POST)) {
$this->c->providers->update($v->form);
return $this->c->Redirect->page('AdminProviders')->message('Providers updated redirect');
}
$this->fIswev = $v->getErrors();
}
$this->mDisabled();
$this->nameTpl = 'admin/form';
$this->aIndex = 'options';
$this->aCrumbs[] = [$this->c->providers->link(''), 'Providers'];
$this->form = $this->formView();
$this->classForm = ['providers', 'inline'];
$this->titleForm = 'Providers';
return $this;
}
/**
* Подготавливает массив данных для формы
*/
protected function formView(): array
{
$form = [
'action' => $this->c->providers->link(''),
'hidden' => [
'token' => $this->c->Csrf->create('AdminProviders'),
],
'sets' => [],
'btns' => [
'save' => [
'type' => 'submit',
'value' => __('Save changes'),
],
],
];
$fields = [];
foreach ($this->c->providers->init()->list() as $provider) {
$fields["name-{$provider->name}"] = [
'class' => ['name', 'provider'],
'type' => 'btn',
'value' => $provider->name,
'caption' => 'Provider label',
'link' => $this->c->providers->link($provider->name),
];
$fields["form[{$provider->name}][pr_pos]"] = [
'class' => ['position', 'provider'],
'type' => 'number',
'min' => '0',
'max' => '9999999999',
'value' => $provider->pos,
'caption' => 'Position label',
];
$fields["form[{$provider->name}][pr_allow]"] = [
'class' => ['allow', 'provider'],
'type' => 'checkbox',
'checked' => $provider->allow,
'caption' => 'Allow label',
];
$form['sets']["provider-{$provider->name}"] = [
'class' => ['provider', 'inline'],
'legend' => $provider->name,
'fields' => $fields,
];
}
return $form;
}
/**
* Просмотр, редактирвоание и добавление категорий
*/
public function edit(array $args, string $method): Page
{
$provider = $this->c->providers->init()->get($args['name']);
if (! $provider instanceof Driver) {
return $this->c->Message->message('Bad request');
}
$this->c->Lang->load('validator');
$this->c->Lang->load('admin_providers');
if ('POST' === $method) {
$v = $this->c->Validator->reset()
->addRules([
'token' => 'token:AdminProvider',
'client_id' => 'exist|string:trim|max:255',
'client_secret' => 'exist|string:trim|max:255',
'changeData' => 'checkbox',
])->addAliases([
])->addArguments([
'token' => $args,
])->addMessages([
]);
if ($v->validation($_POST)) {
if ($v->changeData) {
$this->c->providers->update([
$provider->name => [
'pr_cl_id' => $v->client_id,
'pr_cl_sec' => $v->client_secret,
],
]);
$message = 'Provider updated redirect';
} else {
$message = 'No confirm redirect';
}
return $this->c->Redirect->page('AdminProvider', $args)->message($message);
}
$this->fIswev = $v->getErrors();
}
$this->mDisabled();
$this->nameTpl = 'admin/form';
$this->aIndex = 'options';
$this->aCrumbs[] = [$this->c->providers->link($provider->name), $provider->name];
$this->aCrumbs[] = [$this->c->providers->link(''), 'Providers'];
$this->form = $this->formEdit($provider);
$this->classForm = ['provider'];
$this->titleForm = $provider->name;
return $this;
}
/**
* Подготавливает массив данных для формы
*/
protected function formEdit(Driver $provider): array
{
$form = [
'action' => $this->c->providers->link($provider->name),
'hidden' => [
'token' => $this->c->Csrf->create('AdminProvider', ['name' => $provider->name]),
],
'sets' => [
'provider' => [
'fields' => [
'callback' => [
'type' => 'str',
'value' => $provider->linkCallback,
'caption' => 'Callback label',
'help' => 'Callback help',
],
'client_id' => [
'type' => 'text',
'maxlength' => '255',
'value' => $provider->client_id,
'caption' => 'ID label',
'help' => 'ID help',
],
'client_secret' => [
'type' => 'text',
'maxlength' => '255',
'value' => '' == $provider->client_secret ? '' : '********',
'caption' => 'Secret label',
'help' => 'Secret help',
],
'changeData' => [
'type' => 'checkbox',
'caption' => '',
'label' => 'Change data help',
],
],
]
],
'btns' => [
'save' => [
'type' => 'submit',
'value' => __('Save changes'),
],
],
];
return $form;
}
}

View file

@ -25,7 +25,7 @@ class Update extends Admin
{
const PHP_MIN = '8.0.0';
const REV_MIN_FOR_UPDATE = 53;
const LATEST_REV_WITH_DB_CHANGES = 53;
const LATEST_REV_WITH_DB_CHANGES = 55;
const LOCK_NAME = 'lock_update';
const LOCK_TTL = 1800;
const JSON_OPTIONS = \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR;
@ -410,4 +410,98 @@ class Update extends Admin
#
# return null;
# }
/**
* rev.54 to rev.55
*/
protected function stageNumber54(array $args): ?int
{
$config = $this->c->config;
$config->b_oauth_allow = 0;
$config->save();
$schema = [
'FIELDS' => [
'pr_name' => ['VARCHAR(25)', false],
'pr_allow' => ['TINYINT(1)', false, 0],
'pr_pos' => ['INT(10) UNSIGNED', false, 0],
'pr_cl_id' => ['VARCHAR(255)', false, ''],
'pr_cl_sec' => ['VARCHAR(255)', false, ''],
],
'UNIQUE KEYS' => [
'pr_name_idx' => ['pr_name'],
],
];
$this->c->DB->createTable('::providers', $schema);
$schema = [
'FIELDS' => [
'uid' => ['INT(10) UNSIGNED', false],
'pr_name' => ['VARCHAR(25)', false],
'pu_uid' => ['VARCHAR(165)', false],
'pu_email' => ['VARCHAR(190)', false, ''],
'pu_email_normal' => ['VARCHAR(190)', false, ''],
'pu_email_verified' => ['TINYINT(1)', false, 0],
],
'UNIQUE KEYS' => [
'pr_name_pu_uid_idx' => ['pr_name', 'pu_uid'],
],
'INDEXES' => [
'uid_idx' => ['uid'],
'pu_email_normal_idx' => ['pu_email_normal'],
],
];
$this->c->DB->createTable('::providers_users', $schema);
$providers = [
'github',
];
$query = 'INSERT INTO ::providers (pr_name, pr_pos)
SELECT tmp.*
FROM (SELECT ?s:name AS f1, ?i:pos AS f2) AS tmp
WHERE NOT EXISTS (
SELECT 1
FROM ::providers
WHERE pr_name=?s:name
)';
foreach ($providers as $pos => $name) {
$vars = [
':name' => $name,
':pos' => $pos,
];
$this->c->DB->exec($query, $vars);
}
$coreConfig = new CoreConfig($this->configFile);
$coreConfig->add(
'multiple=>AdminProviders',
'\\ForkBB\\Models\\Pages\\Admin\\Providers::class',
'AdminOptions'
);
$coreConfig->add(
'shared=>providers',
[
'class' => '\\ForkBB\\Models\\Provider\\Providers::class',
'drivers' => [
'github' => '\\ForkBB\\Models\\Provider\\Driver\\GitHub::class'
],
],
'pms'
);
$coreConfig->add(
'multiple=>RegLog',
'\\ForkBB\\Models\\Pages\\RegLog::class',
'Register'
);
$coreConfig->save();
return null;
}
}

View file

@ -13,12 +13,15 @@ namespace ForkBB\Models\Pages;
use ForkBB\Core\Validator;
use ForkBB\Core\Exceptions\MailException;
use ForkBB\Models\Page;
use ForkBB\Models\Pages\RegLogTrait;
use ForkBB\Models\User\User;
use SensitiveParameter;
use function \ForkBB\__;
class Auth extends Page
{
use RegLogTrait;
/**
* Выход пользователя
*/
@ -105,6 +108,7 @@ class Auth extends Page
$save = $v->save ?? true;
$redirect = $v->redirect ?? $this->c->Router->validate($ref, 'Index');
$this->form = $this->formLogin($username, (bool) $save, $redirect);
$this->formOAuth = $this->reglogForm();
return $this;
}

View file

@ -0,0 +1,82 @@
<?php
/**
* This file is part of the ForkBB <https://github.com/forkbb>.
*
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
* @license The MIT License (MIT)
*/
declare(strict_types=1);
namespace ForkBB\Models\Pages;
use ForkBB\Core\Validator;
use ForkBB\Core\Exceptions\MailException;
use ForkBB\Models\Page;
use ForkBB\Models\User\User;
use function \ForkBB\__;
class RegLog extends Page
{
/**
* Обрабатывает нажатие одной из кнопок провайдеров
*/
public function redirect(): Page
{
if (
1 !== $this->c->config->b_oauth_allow
|| empty($list = $this->c->providers->active())
) {
return $this->c->Message->message('Bad request');
}
$rules = [
'token' => 'token:RegLogRedirect',
];
foreach ($list as $name) {
$rules[$name] = 'string';
}
$v = $this->c->Validator->reset()->addRules($rules);
if (
! $v->validation($_POST)
|| 1 !== \count($form = $v->getData(false, ['token']))
) {
return $this->c->Message->message('Bad request');
}
return $this->c->Redirect->url($this->c->providers->init()->get(\array_key_first($form))->linkAuth);
}
/**
* Обрабатывает ответ сервера
*/
public function callback(array $args): Page
{
if (
1 !== $this->c->config->b_oauth_allow
|| empty($list = $this->c->providers->active())
|| empty($list[$args['name']])
) {
return $this->c->Message->message('Bad request');
}
$this->c->Lang->load('admin_providers');
$provider = $this->c->providers->init()->get($args['name']);
if (true !== ($result = $provider->verifyAuth($_GET))) {
return $this->c->Message->message($result);
}
if (true !== $provider->reqAccessToken()) {
return $this->c->Message->message('Error token');
}
if (true !== $provider->reqUserInfo()) {
}
}
}

View file

@ -0,0 +1,47 @@
<?php
/**
* This file is part of the ForkBB <https://github.com/forkbb>.
*
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
* @license The MIT License (MIT)
*/
declare(strict_types=1);
namespace ForkBB\Models\Pages;
use ForkBB\Models\Model;
use function \ForkBB\__;
trait RegLogTrait
{
protected function reglogForm(): array
{
if (
1 !== $this->c->config->b_oauth_allow
|| empty($list = $this->c->providers->active())
) {
return [];
}
$this->c->Lang->load('admin_providers');
$btns = [];
foreach ($list as $name) {
$btns[$name] = [
'type' => 'submit',
'value' => __(['Sign in with %s', __($name)]),
];
}
return [
'action' => $this->c->Router->link('RegLogRedirect'),
'hidden' => [
'token' => $this->c->Csrf->create('RegLogRedirect'),
],
'sets' => [],
'btns' => $btns,
];
}
}

View file

@ -13,11 +13,14 @@ namespace ForkBB\Models\Pages;
use ForkBB\Core\Validator;
use ForkBB\Core\Exceptions\MailException;
use ForkBB\Models\Page;
use ForkBB\Models\Pages\RegLogTrait;
use ForkBB\Models\User\User;
use function \ForkBB\__;
class Register extends Page
{
use RegLogTrait;
/**
* Регистрация
*/
@ -85,6 +88,7 @@ class Register extends Page
$this->titles = 'Register';
$this->robots = 'noindex';
$this->form = $this->formReg($v);
$this->formOAuth = $this->reglogForm();
return $this;
}

View file

@ -0,0 +1,162 @@
<?php
/**
* This file is part of the ForkBB <https://github.com/forkbb>.
*
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
* @license The MIT License (MIT)
*/
declare(strict_types=1);
namespace ForkBB\Models\Provider;
use ForkBB\Core\Container;
use ForkBB\Models\Model;
use RuntimeException;
abstract class Driver extends Model
{
/**
* Ключ модели для контейнера
*/
protected string $cKey = 'Provider';
protected string $code;
protected string $originalName;
protected string $authURL;
protected string $tokenURL;
protected string $userURL;
protected string $scope;
public function __construct(protected string $client_id, protected string $client_secret, Container $c)
{
parent::__construct($c);
}
protected function setname(string $name):void
{
if ($this->originalName !== $name) {
throw new RuntimeException("Invalid name: {$name}");
}
$this->setAttr('name', $name);
}
protected function getlinkCallback(): string
{
return $this->c->Router->link('RegLogCallback', ['name' => $this->name]);
}
protected function getclient_id(): string
{
return $this->client_id;
}
protected function getclient_secret(): string
{
return $this->client_secret;
}
protected function getlinkAuth(): string
{
$params = [
'response_type' => 'code',
'scope' => $this->scope,
'state' => $this->c->Csrf->createHash($this->originalName, ['ip' => $this->c->user->ip]),
'client_id' => $this->client_id,
'redirect_uri' => $this->linkCallback,
];
return $this->authURL . '?' . \http_build_query($params);
}
protected function verifyState(string $state): bool
{
return $this->c->Csrf->verify($state, $this->originalName, ['ip' => $this->c->user->ip]);
}
public function verifyAuth(array $data): bool|string|array
{
if (! \is_string($data['code'] ?? null)) {
if (\is_string($data['error'] ?? null)) {
return ['Provider response: %s', $data['error']];
} else {
return ['Provider response: %s', 'undefined'];
}
}
if (
! \is_string($data['state'] ?? null)
|| ! $this->verifyState($data['state'])
) {
return 'State error';
}
$this->code = $data['code'];
return true;
}
public function reqAccessToken(): bool
{
$params = [
'grant_type' => 'authorization_code',
'client_id' => $this->client_id,
'client_secret' => $this->client_secret,
'code' => $this->code,
'redirect_uri' => $this->linkCallback,
];
$ch = \curl_init($this->tokenURL);
\curl_setopt($ch, \CURLOPT_HTTPHEADER, ['Accept: application/json']);
\curl_setopt($ch, \CURLOPT_POST, true);
\curl_setopt($ch, \CURLOPT_POSTFIELDS, \http_build_query($params));
\curl_setopt($ch, \CURLOPT_RETURNTRANSFER, true);
\curl_setopt($ch, \CURLOPT_HEADER, false);
$html = \curl_exec($ch);
# $type = \curl_getinfo($ch, \CURLINFO_CONTENT_TYPE);
\curl_close($ch);
if (
! isset($html[1])
|| '{' !== $html[0]
|| '}' !== $html[-1]
|| ! \is_array($json = \json_decode($html, true, 20, \JSON_BIGINT_AS_STRING & JSON_UNESCAPED_UNICODE & JSON_INVALID_UTF8_SUBSTITUTE))
|| ! isset($json['access_token'])
) {
return false;
}
$this->access_token = $json['access_token'];
return true;
}
public function reqUserInfo(): bool
{
$headers = [
'Accept: application/json',
"Authorization: Bearer {$this->access_token}",
'User-Agent: ForkBB (Client ID: {$this->client_id})',
];
$ch = \curl_init($this->userURL);
\curl_setopt($ch, \CURLOPT_HTTPHEADER, $headers);
\curl_setopt($ch, \CURLOPT_POST, false);
#\curl_setopt($ch, \CURLOPT_POSTFIELDS, \http_build_query($params));
\curl_setopt($ch, \CURLOPT_RETURNTRANSFER, true);
\curl_setopt($ch, \CURLOPT_HEADER, false);
$html = \curl_exec($ch);
# $type = \curl_getinfo($ch, \CURLINFO_CONTENT_TYPE);
\curl_close($ch);
exit(var_dump("<pre>".$html));
}
}

View file

@ -0,0 +1,24 @@
<?php
/**
* This file is part of the ForkBB <https://github.com/forkbb>.
*
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
* @license The MIT License (MIT)
*/
declare(strict_types=1);
namespace ForkBB\Models\Provider\Driver;
use ForkBB\Models\Provider\Driver;
use RuntimeException;
class GitHub extends Driver
{
protected string $originalName = 'github';
protected string $authURL = 'https://github.com/login/oauth/authorize';
protected string $tokenURL = 'https://github.com/login/oauth/access_token';
protected string $userURL = 'https://api.github.com/user';
protected string $scope = 'read:user';
}

View file

@ -0,0 +1,192 @@
<?php
/**
* This file is part of the ForkBB <https://github.com/forkbb>.
*
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
* @license The MIT License (MIT)
*/
declare(strict_types=1);
namespace ForkBB\Models\Provider;
use ForkBB\Core\Container;
use ForkBB\Models\Manager;
use ForkBB\Models\Provider\Driver;
use RuntimeException;
class Providers extends Manager
{
const CACHE_KEY = 'providers';
/**
* Ключ модели для контейнера
*/
protected string $cKey = 'Providers';
protected ?array $cache = null;
protected bool $ready = false;
public function __construct(protected array $drivers, Container $c)
{
parent::__construct($c);
}
public function init(): self
{
if (! $this->ready) {
foreach ($this->cache() as $cur) {
$this->create($cur);
}
$this->ready = true;
}
return $this;
}
public function link(string $name): string
{
return '' === $name
? $this->c->Router->link('AdminProviders')
: $this->c->Router->link('AdminProvider', ['name' => $name]);
}
public function create(array $attrs = []): Driver
{
if (! isset($attrs['pr_name'])) {
throw new RuntimeException('Provider name missing');
}
if (! isset($this->drivers[$attrs['pr_name']])) {
throw new RuntimeException("No driver for '{$attrs['pr_name']}' provider");
}
if ($this->isset($attrs['pr_name'])) {
throw new RuntimeException("Driver '{$attrs['pr_name']}' already exists");
}
$class = $this->drivers[$attrs['pr_name']];
$driver = new $class($attrs['pr_cl_id'], $attrs['pr_cl_sec'], $this->c);
$driver->name = $attrs['pr_name'];
$driver->pos = $attrs['pr_pos'];
$driver->allow = $attrs['pr_allow'];
$this->set($driver->name, $driver);
return $driver;
}
public function list(): array
{
return $this->repository;
}
public function active(): array
{
$result = [];
foreach ($this->cache() as $cur) {
if (
$cur['pr_allow']
&& '' != $cur['pr_cl_id']
&& '' != $cur['pr_cl_sec']
) {
$result[$cur['pr_name']] = $cur['pr_name'];
}
}
return $result;
}
protected function cache(): array
{
if (! \is_array($this->cache)) {
$this->cache = $this->c->Cache->get(self::CACHE_KEY);
}
if (! \is_array($this->cache)) {
$query = 'SELECT pr_name, pr_allow, pr_pos, pr_cl_id, pr_cl_sec
FROM ::providers
ORDER BY pr_pos';
$stmt = $this->c->DB->query($query);
while ($cur = $stmt->fetch()) {
$this->cache[$cur['pr_name']] = $cur;
}
if (true !== $this->c->Cache->set(self::CACHE_KEY, $this->cache)) {
throw new RuntimeException('Unable to write value to cache - ' . self::CACHE_KEY);
}
}
return $this->cache;
}
protected function emptyField(string $key, array $sets, array $cache): bool
{
if (isset($sets[$key])) {
return '' == $sets[$key];
} else {
return '' == $cache[$key];
}
}
public function update(array $form): self
{
$cache = $this->cache();
$fields = $this->c->dbMap->providers;
foreach ($form as $driver => $sets) {
if (! isset($this->drivers[$driver], $cache[$driver])) {
continue;
}
$set = $vars = [];
if (
(
! empty($sets['pr_allow'])
|| ! empty($cache[$driver]['pr_allow'])
)
&& (
$this->emptyField('pr_cl_id', $sets, $cache[$driver])
|| $this->emptyField('pr_cl_sec', $sets, $cache[$driver])
)
) {
$sets['pr_allow'] = 0;
}
foreach ($sets as $name => $value) {
if (
! isset($fields[$name])
|| $cache[$driver][$name] == $value
) {
continue;
}
$vars[] = $value;
$set[] = $name . '=?' . $fields[$name];
}
if (empty($set)) {
continue;
}
$vars[] = $driver;
$set = \implode(', ', $set);
$query = "UPDATE ::providers
SET {$set}
WHERE pr_name=?s";
$this->c->DB->exec($query, $vars);
}
if (true !== $this->c->Cache->delete(self::CACHE_KEY)) {
throw new RuntimeException('Unable to delete cache - ' . self::CACHE_KEY);
}
return $this;
}
}

View file

@ -163,6 +163,12 @@ return [
'subscriptions' => \ForkBB\Models\Subscription\Subscription::class,
'bbcode' => '@BBCodeListModel:init',
'pms' => \ForkBB\Models\PM\PM::class,
'providers' => [
'class' => \ForkBB\Models\Provider\Providers::class,
'drivers' => [
'github' => \ForkBB\Models\Provider\Driver\GitHub::class,
],
],
'Csrf' => [
'class' => \ForkBB\Core\Csrf::class,
@ -333,6 +339,7 @@ return [
'Userlist' => \ForkBB\Models\Pages\Userlist::class,
'Search' => \ForkBB\Models\Pages\Search::class,
'Register' => \ForkBB\Models\Pages\Register::class,
'RegLog' => \ForkBB\Models\Pages\RegLog::class,
'Redirect' => \ForkBB\Models\Pages\Redirect::class,
'Maintenance' => \ForkBB\Models\Pages\Maintenance::class,
'Ban' => \ForkBB\Models\Pages\Ban::class,
@ -360,6 +367,7 @@ return [
'AdminIndex' => \ForkBB\Models\Pages\Admin\Index::class,
'AdminStatistics' => \ForkBB\Models\Pages\Admin\Statistics::class,
'AdminOptions' => \ForkBB\Models\Pages\Admin\Options::class,
'AdminProviders' => \ForkBB\Models\Pages\Admin\Providers::class,
'AdminCategories' => \ForkBB\Models\Pages\Admin\Categories::class,
'AdminForums' => \ForkBB\Models\Pages\Admin\Forums::class,
'AdminGroups' => \ForkBB\Models\Pages\Admin\Groups::class,

View file

@ -425,3 +425,12 @@ msgstr "Allow private messages"
msgid "Allow PM help"
msgstr "Settings for individual groups are available in <a href="%2$s">%1$s</a>."
msgid "Allow oauth label"
msgstr "OAuth-authorization"
msgid "Allow oauth help"
msgstr "Allow users to login/register on this bulletin board using accounts on third-party resources."
msgid "Configure providers"
msgstr "Configure providers"

View file

@ -0,0 +1,70 @@
#
msgid ""
msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Project-Id-Version: ForkBB\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: ForkBB <mio.visman@yandex.ru>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en\n"
msgid "OAuth authorization disabled"
msgstr "OAuth authorization <a href=\"%s\">disabled</a>."
msgid "Providers"
msgstr "Providers"
msgid "Provider label"
msgstr "Provider"
msgid "Position label"
msgstr "Position"
msgid "Allow label"
msgstr "Allow"
msgid "Providers updated redirect"
msgstr "Provider table updated."
msgid "Provider updated redirect"
msgstr "Provider data updated."
msgid "github"
msgstr "GitHub"
msgid "Callback label"
msgstr "Callback URI"
msgid "Callback help"
msgstr "The URI that the user is returned to after they have granted or denied access to the application (corresponds to the redirect_uri of the OAuth protocol). It must be specified when registering your application on the provider's server."
msgid "ID label"
msgstr "Client ID"
msgid "ID help"
msgstr "Identifier that you can get when registering your application on the provider's server."
msgid "Secret label"
msgstr "Client secret"
msgid "Secret help"
msgstr "The secret (password) that you can get when registering your application on the provider's server."
msgid "Change data help"
msgstr "Enable this checkbox if you want to change the data"
msgid "Sign in with %s"
msgstr "Sign in with %s"
msgid "Provider response: %s"
msgstr "Provider response: %s."
msgid "State error"
msgstr "Error in state parameter."
msgid "Error token"
msgstr "Error request access token"

View file

@ -280,7 +280,7 @@ msgid "SMTP password label"
msgstr "SMTP password"
msgid "SMTP change password help"
msgstr "Установите галку, если хотите поменять или удалить пароль"
msgstr "Установите флажок, если хотите поменять или удалить пароль"
msgid "SMTP password help"
msgstr "Пароль для сервера SMTP. Заполняйте только если сервер этого требует."
@ -425,3 +425,12 @@ msgstr "Разрешить личные сообщения"
msgid "Allow PM help"
msgstr "Настройки для отдельных групп доступны в <a href="%2$s">%1$s</a>."
msgid "Allow oauth label"
msgstr "OAuth-авторизация"
msgid "Allow oauth help"
msgstr "Разрешить пользователям вход/регистрацию на форуме с помощью аккаунтов на сторонних ресурсах."
msgid "Configure providers"
msgstr "Настроить провайдеров"

View file

@ -0,0 +1,70 @@
#
msgid ""
msgstr ""
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"Project-Id-Version: ForkBB\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: ForkBB <mio.visman@yandex.ru>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru\n"
msgid "OAuth authorization disabled"
msgstr "OAuth-авторизация <a href=\"%s\">выключена</a>."
msgid "Providers"
msgstr "Провайдеры"
msgid "Provider label"
msgstr "Провайдер"
msgid "Position label"
msgstr "Позиция"
msgid "Allow label"
msgstr "Вкл."
msgid "Providers updated redirect"
msgstr "Таблица провайдеров обновлена."
msgid "Provider updated redirect"
msgstr "Данные провайдера обновлены."
msgid "github"
msgstr "GitHub"
msgid "Callback label"
msgstr "URI перенаправления"
msgid "Callback help"
msgstr "Адрес, на который пользователь возращается после того, как он разрешил или отказал приложению в доступе (соответствует redirect_uri протокола OAuth). Его нужно указать при регистрации своего приложения на сервере провайдера."
msgid "ID label"
msgstr "Идентификатор клиента"
msgid "ID help"
msgstr "Идентификатор (ID), который можно получить при регистрации своего приложения на сервере провайдера."
msgid "Secret label"
msgstr "Секрет клиента"
msgid "Secret help"
msgstr "Секрет (пароль), который можно получить при регистрации своего приложения на сервере провайдера."
msgid "Change data help"
msgstr "Установите флажок, если хотите изменить данные"
msgid "Sign in with %s"
msgstr "Войти с помощью %s"
msgid "Provider response: %s"
msgstr "Ответ провайдера: %s."
msgid "State error"
msgstr "Ошибка в параметре state."
msgid "Error token"
msgstr "Ошибка запроса токена доступа"

View file

@ -14,3 +14,10 @@
@endif
</section>
@endif
@if ($form = $p->formOAuth)
<div id="fork-oauth" class="f-main">
<div class="f-fdiv f-lrdiv">
@include ('layouts/form')
</div>
</div>
@endif

View file

@ -7,3 +7,10 @@
</div>
</section>
@endif
@if ($form = $p->formOAuth)
<div id="fork-oauth" class="f-main">
<div class="f-fdiv f-lrdiv">
@include ('layouts/form')
</div>
</div>
@endif

View file

@ -208,7 +208,9 @@
}
#forka .f-fs-inline dl {
display: flex;
flex-direction: column;
justify-content: space-between;
}
#forka .f-fs-inline dt {
@ -1026,3 +1028,34 @@
flex-wrap: wrap;
}
}
/****************************************/
/* Админка/Провайдеры */
/****************************************/
#forka .f-field-provider.f-field-name {
width: calc(100% - 8rem);
}
#forka .f-field-provider.f-field-position {
width: 5rem;
}
#forka .f-field-provider.f-field-allow {
width: 3rem;
overflow: hidden;
text-align: center;
}
#forka .f-field-provider.f-field-name .f-ybtn {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#forka .f-field-provider.f-field-allow .f-ychk {
margin: 0;
}
#forka .f-field-provider.f-field-allow .f-flblch {
height: 2rem;
}