User.init(
{@$__wcf->user->userID},
{if $__wcf->user->userID}'{@$__wcf->user->username|encodeJS}'{else}''{/if},
- {if $__wcf->user->userID}'{@$__wcf->user->getLink()|encodeJS}'{else}''{/if}
+ {if $__wcf->user->userID}'{@$__wcf->user->getLink()|encodeJS}'{else}''{/if},
+ '{link controller='GuestTokenDialog'}{/link}'
);
BootstrapFrontend.setup({
--- /dev/null
+/**
+ * Handles the creation of guest tokens.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+
+import { dialogFactory } from "WoltLabSuite/Core/Component/Dialog";
+import User from "WoltLabSuite/Core/User";
+
+type Response = {
+ token: string;
+};
+
+export async function getGuestToken(): Promise<string | undefined> {
+ const { ok, result } = await dialogFactory().usingFormBuilder().fromEndpoint<Response>(User.guestTokenDialogEndpoint);
+
+ if (ok) {
+ return result.token;
+ }
+
+ return undefined;
+}
readonly userId: number,
readonly username: string,
readonly link: string,
+ readonly guestTokenDialogEndpoint: string,
) {}
}
/**
* Initializes the user object.
*/
- init(userId: number, username: string, link: string): void {
+ init(userId: number, username: string, link: string, guestTokenDialogEndpoint: string = ""): void {
if (user) {
throw new Error("User has already been initialized.");
}
- user = new User(userId, username, link);
+ user = new User(userId, username, link, guestTokenDialogEndpoint);
},
get userId(): number {
get username(): string {
return user.username;
},
+
+ get guestTokenDialogEndpoint(): string {
+ return user.guestTokenDialogEndpoint;
+ },
};
--- /dev/null
+/**
+ * Handles the creation of guest tokens.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+define(["require", "exports", "tslib", "WoltLabSuite/Core/Component/Dialog", "WoltLabSuite/Core/User"], function (require, exports, tslib_1, Dialog_1, User_1) {
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.getGuestToken = void 0;
+ User_1 = tslib_1.__importDefault(User_1);
+ async function getGuestToken() {
+ const { ok, result } = await (0, Dialog_1.dialogFactory)().usingFormBuilder().fromEndpoint(User_1.default.guestTokenDialogEndpoint);
+ if (ok) {
+ return result.token;
+ }
+ return undefined;
+ }
+ exports.getGuestToken = getGuestToken;
+});
userId;
username;
link;
- constructor(userId, username, link) {
+ guestTokenDialogEndpoint;
+ constructor(userId, username, link, guestTokenDialogEndpoint) {
this.userId = userId;
this.username = username;
this.link = link;
+ this.guestTokenDialogEndpoint = guestTokenDialogEndpoint;
}
}
let user;
/**
* Initializes the user object.
*/
- init(userId, username, link) {
+ init(userId, username, link, guestTokenDialogEndpoint = "") {
if (user) {
throw new Error("User has already been initialized.");
}
- user = new User(userId, username, link);
+ user = new User(userId, username, link, guestTokenDialogEndpoint);
},
get userId() {
return user.userId;
get username() {
return user.username;
},
+ get guestTokenDialogEndpoint() {
+ return user.guestTokenDialogEndpoint;
+ },
};
});
--- /dev/null
+<?php
+
+namespace wcf\action;
+
+use Laminas\Diactoros\Response\JsonResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\form\builder\field\CaptchaFormField;
+use wcf\system\form\builder\field\user\UsernameFormField;
+use wcf\system\form\builder\Psr15DialogForm;
+use wcf\system\WCF;
+use wcf\util\UserUtil;
+
+/**
+ * Displays a dialog that guests can use to generate a guest token to authorize themselves for certain actions.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+final class GuestTokenDialogAction implements RequestHandlerInterface
+{
+ /**
+ * @inheritDoc
+ */
+ public function handle(ServerRequestInterface $request): ResponseInterface
+ {
+ if (WCF::getUser()->userID) {
+ throw new PermissionDeniedException();
+ }
+
+ $form = $this->getForm();
+
+ if ($request->getMethod() === 'GET') {
+ return $form->toResponse();
+ } elseif ($request->getMethod() === 'POST') {
+ $response = $form->validateRequest($request);
+ if ($response !== null) {
+ return $response;
+ }
+
+ $data = $form->getData()['data'];
+
+ return new JsonResponse([
+ 'result' => [
+ 'token' => UserUtil::createGuestToken($data['username']),
+ ],
+ ]);
+ } else {
+ throw new \LogicException('Unreachable');
+ }
+ }
+
+ private function getForm(): Psr15DialogForm
+ {
+ $form = new Psr15DialogForm(
+ static::class,
+ WCF::getLanguage()->get('wcf.page.guestTokenDialog.title')
+ );
+ $form->appendChildren([
+ UsernameFormField::create()
+ ->required(),
+ CaptchaFormField::create()
+ ->objectType(\CAPTCHA_TYPE),
+ ]);
+
+ $form->markRequiredFields(false);
+ $form->build();
+
+ return $form;
+ }
+}
namespace wcf\util;
use wcf\system\email\Mailbox;
+use wcf\system\exception\SystemException;
use wcf\system\WCF;
/**
return \mb_substr(FileUtil::unifyDirSeparator($REQUEST_URI), 0, 255);
}
+ /**
+ * Creates a guest token for the given username.
+ *
+ * @since 6.1
+ */
+ public static function createGuestToken(string $username): string
+ {
+ return CryptoUtil::createSignedString(JSON::encode(
+ [
+ 'username' => $username,
+ 'time' => TIME_NOW,
+ ]
+ ));
+ }
+
+ /**
+ * Verifies the given guest token and returns the stored username if the token is valid,
+ * otherwise returns null.
+ *
+ * @since 6.1
+ */
+ public static function verifyGuestToken(string $token): ?string
+ {
+ if ($token === '') {
+ return null;
+ }
+
+ $json = CryptoUtil::getValueFromSignedString($token);
+ if ($json === null) {
+ return null;
+ }
+
+ try {
+ $data = JSON::decode($json);
+ } catch (SystemException $e) {
+ return null;
+ }
+
+ if (!\is_array($data) || !isset($data['username']) || !isset($data['time'])) {
+ return null;
+ }
+
+ if ($data['time'] < \TIME_NOW - 30) {
+ return null;
+ }
+
+ return $data['username'];
+ }
+
/**
* Forbid creation of UserUtil objects.
*/
<item name="wcf.page.requestedPage.condition.reverseLogic.description"><![CDATA[Wenn ausgewählt, darf die aufgerufene Seite <strong>keine</strong> der unter „Aufgerufene Seite“ ausgewählten Seiten sein.]]></item>
<item name="wcf.page.box.edit"><![CDATA[Box bearbeiten]]></item>
<item name="wcf.page.menu.outstandingItems"><![CDATA[({#$menuItemNode->getOutstandingItems()} {if $menuItemNode->getOutstandingItems() == 1}neuer Eintrag{else}neue Einträge{/if})]]></item>
+ <item name="wcf.page.guestTokenDialog.title"><![CDATA[Verifizierung erforderlich]]></item>
</category>
<category name="wcf.paidSubscription">
<item name="wcf.paidSubscription.availableSubscriptions"><![CDATA[Verfügbare Mitgliedschaften]]></item>
<item name="wcf.page.requestedPage.condition.reverseLogic.description"><![CDATA[If selected, the requested page <strong>may not</strong> be one of the pages selected under “Requested Page”.]]></item>
<item name="wcf.page.box.edit"><![CDATA[Edit Box]]></item>
<item name="wcf.page.menu.outstandingItems"><![CDATA[({#$menuItemNode->getOutstandingItems()} {if $menuItemNode->getOutstandingItems() == 1}New Item{else}News Items{/if})]]></item>
+ <item name="wcf.page.guestTokenDialog.title"><![CDATA[Verification Required]]></item>
</category>
<category name="wcf.acp.page">
<item name="wcf.acp.page.add"><![CDATA[Add Page]]></item>