mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-01-01 14:07:54 +01:00
Added Login & Registration pages (#1422)
- Added login.html and register.html - Added .bg-gradient - Added logonsine.js for a decoration. - Added /login, /register, /auth/login, /auth/logout and /auth/register endpoints - Redirects to /login if cookie not present with auth enabled. - Basic login functionality using cookies - Registration page allows new kind of registration that doesn't log passwords on console. - Fixes a bug with stippets that blocked any cross-plugin modifications (PageExtension API) - Fixes a typo with css snippet code that made the css not apply (PageExtension API)
This commit is contained in:
parent
3d85a23eb3
commit
3903a266a3
@ -68,4 +68,20 @@ public final class URIQuery {
|
||||
public Optional<String> get(String key) {
|
||||
return Optional.ofNullable(byKey.get(key));
|
||||
}
|
||||
|
||||
public String asString() {
|
||||
StringBuilder builder = new StringBuilder("?");
|
||||
int i = 0;
|
||||
int max = byKey.size();
|
||||
for (Map.Entry<String, String> entry : byKey.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
builder.append(key).append('=').append(value);
|
||||
if (i < max - 1) {
|
||||
builder.append('&');
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ package com.djrapitops.plan.commands.subcommands;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.WebUser;
|
||||
import com.djrapitops.plan.delivery.webserver.Addresses;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.FailReason;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.RegistrationBin;
|
||||
import com.djrapitops.plan.exceptions.database.DBOpException;
|
||||
import com.djrapitops.plan.processing.Processing;
|
||||
import com.djrapitops.plan.settings.Permissions;
|
||||
@ -42,6 +44,8 @@ import com.djrapitops.plugin.utilities.Verify;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
/**
|
||||
@ -104,10 +108,23 @@ public class RegisterCommand extends CommandNode {
|
||||
throw new IllegalArgumentException(locale.getString(CommandLang.FAIL_REQ_ARGS, 1, Arrays.toString(getArguments())));
|
||||
}
|
||||
|
||||
if (CommandUtils.isPlayer(sender)) {
|
||||
playerRegister(args, sender);
|
||||
List<String> argumentList = Arrays.asList(args);
|
||||
boolean newRegister = argumentList.contains("--code");
|
||||
if (newRegister) {
|
||||
if (CommandUtils.isPlayer(sender)) {
|
||||
register(argumentList, getPermissionLevel(sender), sender);
|
||||
} else if (argumentList.contains("superuser")) {
|
||||
register(argumentList, 0, sender);
|
||||
} else {
|
||||
sender.sendMessage("§cInvalid arguments.");
|
||||
}
|
||||
} else {
|
||||
consoleRegister(args, sender, notEnoughArgsMsg);
|
||||
// Legacy support
|
||||
if (CommandUtils.isPlayer(sender)) {
|
||||
playerRegister(args, sender);
|
||||
} else {
|
||||
consoleRegister(args, sender, notEnoughArgsMsg);
|
||||
}
|
||||
}
|
||||
} catch (PassEncryptUtil.CannotPerformOperationException e) {
|
||||
errorHandler.log(L.WARN, this.getClass(), e);
|
||||
@ -131,6 +148,22 @@ public class RegisterCommand extends CommandNode {
|
||||
registerUser(new WebUser(userName, passHash, permLevel), sender);
|
||||
}
|
||||
|
||||
private void register(List<String> args, int permissionLevel, Sender sender) {
|
||||
String code = "";
|
||||
for (String arg : args) {
|
||||
if (arg.length() == 12) code = arg;
|
||||
}
|
||||
if (code.isEmpty()) {
|
||||
throw new IllegalArgumentException(locale.getString(CommandLang.FAIL_REQ_ARGS, 1, "--code !<code>!"));
|
||||
}
|
||||
Optional<WebUser> user = RegistrationBin.register(code, permissionLevel);
|
||||
if (!user.isPresent()) {
|
||||
sender.sendMessage("§c" + locale.getString(FailReason.USER_DOES_NOT_EXIST));
|
||||
} else {
|
||||
registerUser(user.get(), sender);
|
||||
}
|
||||
}
|
||||
|
||||
private void playerRegister(String[] args, Sender sender) throws PassEncryptUtil.CannotPerformOperationException {
|
||||
boolean registerSenderAsUser = args.length == 1;
|
||||
if (registerSenderAsUser) {
|
||||
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.rendering.pages;
|
||||
|
||||
import com.djrapitops.plan.delivery.formatting.PlaceholderReplacer;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plugin.api.Check;
|
||||
|
||||
/**
|
||||
* Html String generator for /login and /register page.
|
||||
*
|
||||
* @author Rsl1122
|
||||
*/
|
||||
public class LoginPage implements Page {
|
||||
|
||||
private final String template;
|
||||
private final ServerInfo serverInfo;
|
||||
|
||||
LoginPage(
|
||||
String htmlTemplate,
|
||||
ServerInfo serverInfo
|
||||
) {
|
||||
this.template = htmlTemplate;
|
||||
this.serverInfo = serverInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toHtml() {
|
||||
PlaceholderReplacer placeholders = new PlaceholderReplacer();
|
||||
placeholders.put("command", getCommand());
|
||||
return placeholders.apply(template);
|
||||
}
|
||||
|
||||
private String getCommand() {
|
||||
if (serverInfo.getServer().isNotProxy()) return "plan";
|
||||
if (Check.isBungeeAvailable()) return "planbungee";
|
||||
if (Check.isVelocityAvailable()) return "planvelocity";
|
||||
return "plan";
|
||||
}
|
||||
}
|
@ -214,4 +214,12 @@ public class PageFactory {
|
||||
throw readFail.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
public Page loginPage() throws IOException {
|
||||
return new LoginPage(getResource("login.html"), serverInfo.get());
|
||||
}
|
||||
|
||||
public Page registerPage() throws IOException {
|
||||
return new LoginPage(getResource("register.html"), serverInfo.get());
|
||||
}
|
||||
}
|
@ -94,7 +94,7 @@ public class ResourceSvc implements ResourceService {
|
||||
}
|
||||
|
||||
private WebResource applySnippets(String pluginName, String fileName, WebResource resource) {
|
||||
Map<Position, StringBuilder> byPosition = calculateSnippets(pluginName, fileName);
|
||||
Map<Position, StringBuilder> byPosition = calculateSnippets(fileName);
|
||||
if (byPosition.isEmpty()) return resource;
|
||||
|
||||
String html = applySnippets(resource, byPosition);
|
||||
@ -129,10 +129,10 @@ public class ResourceSvc implements ResourceService {
|
||||
return html;
|
||||
}
|
||||
|
||||
private Map<Position, StringBuilder> calculateSnippets(String pluginName, String fileName) {
|
||||
private Map<Position, StringBuilder> calculateSnippets(String fileName) {
|
||||
Map<Position, StringBuilder> byPosition = new EnumMap<>(Position.class);
|
||||
for (Snippet snippet : snippets) {
|
||||
if (snippet.matches(pluginName, fileName)) {
|
||||
if (snippet.matches(fileName)) {
|
||||
byPosition.computeIfAbsent(snippet.position, k -> new StringBuilder()).append(snippet.content);
|
||||
}
|
||||
}
|
||||
@ -186,7 +186,9 @@ public class ResourceSvc implements ResourceService {
|
||||
.appendWithSeparators(jsSrcs, "\"></script><script src=\"")
|
||||
.append("\"></script>").build();
|
||||
snippets.add(new Snippet(pluginName, fileName, position, snippet));
|
||||
logger.info(locale.getString(PluginLang.API_ADD_RESOURCE_JS, pluginName, fileName, position.cleanName()));
|
||||
if (!"Plan".equals(pluginName)) {
|
||||
logger.info(locale.getString(PluginLang.API_ADD_RESOURCE_JS, pluginName, fileName, position.cleanName()));
|
||||
}
|
||||
}
|
||||
|
||||
public void checkParams(String pluginName, String fileName, Position position, String[] jsSrcs) {
|
||||
@ -212,10 +214,12 @@ public class ResourceSvc implements ResourceService {
|
||||
checkParams(pluginName, fileName, position, cssSrcs);
|
||||
|
||||
String snippet = new TextStringBuilder("<link href=\"")
|
||||
.appendWithSeparators(cssSrcs, "\" ref=\"stylesheet\"></link><link href=\"")
|
||||
.append("\" ref=\"stylesheet\"></link>").build();
|
||||
.appendWithSeparators(cssSrcs, "\" rel=\"stylesheet\"></link><link href=\"")
|
||||
.append("\" rel=\"stylesheet\">").build();
|
||||
snippets.add(new Snippet(pluginName, fileName, position, snippet));
|
||||
logger.info(locale.getString(PluginLang.API_ADD_RESOURCE_CSS, pluginName, fileName, position.cleanName()));
|
||||
if (!"Plan".equals(pluginName)) {
|
||||
logger.info(locale.getString(PluginLang.API_ADD_RESOURCE_CSS, pluginName, fileName, position.cleanName()));
|
||||
}
|
||||
}
|
||||
|
||||
private static class Snippet {
|
||||
@ -231,8 +235,8 @@ public class ResourceSvc implements ResourceService {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public boolean matches(String pluginName, String fileName) {
|
||||
return pluginName.equals(this.pluginName) && fileName.equals(this.fileName);
|
||||
public boolean matches(String fileName) {
|
||||
return fileName.equals(this.fileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -23,6 +23,7 @@ import com.djrapitops.plan.delivery.web.resolver.request.URIQuery;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.Authentication;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.BasicAuthentication;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.CookieAuthentication;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.FailReason;
|
||||
import com.djrapitops.plan.exceptions.WebUserAuthException;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
@ -91,6 +92,7 @@ public class RequestHandler implements HttpHandler {
|
||||
Response response = getResponse(exchange);
|
||||
response.getHeaders().putIfAbsent("Access-Control-Allow-Origin", config.get(WebserverSettings.CORS_ALLOW_ORIGIN));
|
||||
response.getHeaders().putIfAbsent("Access-Control-Allow-Methods", "GET, OPTIONS");
|
||||
response.getHeaders().putIfAbsent("Access-Control-Allow-Credentials", "true");
|
||||
ResponseSender sender = new ResponseSender(addresses, exchange, response);
|
||||
sender.send();
|
||||
} catch (Exception e) {
|
||||
@ -115,10 +117,17 @@ public class RequestHandler implements HttpHandler {
|
||||
response = responseResolver.getResponse(request);
|
||||
}
|
||||
} catch (WebUserAuthException thrownByAuthentication) {
|
||||
if (thrownByAuthentication.getFailReason() != FailReason.USER_AND_PASS_NOT_SPECIFIED) {
|
||||
FailReason failReason = thrownByAuthentication.getFailReason();
|
||||
if (failReason == FailReason.USER_PASS_MISMATCH) {
|
||||
bruteForceGuard.increaseAttemptCountOnFailedLogin(accessor);
|
||||
response = responseFactory.badRequest(failReason.getReason(), "/auth/login");
|
||||
} else {
|
||||
String from = exchange.getRequestURI().toASCIIString();
|
||||
response = Response.builder()
|
||||
.redirectTo(StringUtils.startsWithAny(from, "/auth/", "/login") ? "/login" : "/login?from=" + from)
|
||||
.setHeader("Set-Cookie", "auth=expired; Path=/; Max-Age=1")
|
||||
.build();
|
||||
}
|
||||
response = responseFactory.basicAuthFail(thrownByAuthentication);
|
||||
}
|
||||
|
||||
if (bruteForceGuard.shouldPreventRequest(accessor)) {
|
||||
@ -143,7 +152,7 @@ public class RequestHandler implements HttpHandler {
|
||||
}
|
||||
|
||||
private WebUser getWebUser(HttpExchange exchange) {
|
||||
return getAuthorization(exchange.getRequestHeaders())
|
||||
return getAuthentication(exchange.getRequestHeaders())
|
||||
.map(Authentication::getWebUser) // Can throw WebUserAuthException
|
||||
.map(com.djrapitops.plan.delivery.domain.WebUser::toNewWebUser)
|
||||
.orElse(null);
|
||||
@ -158,11 +167,23 @@ public class RequestHandler implements HttpHandler {
|
||||
return headers;
|
||||
}
|
||||
|
||||
private Optional<Authentication> getAuthorization(Headers requestHeaders) {
|
||||
private Optional<Authentication> getAuthentication(Headers requestHeaders) {
|
||||
if (config.isTrue(WebserverSettings.DISABLED_AUTHENTICATION)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
List<String> cookies = requestHeaders.get("Cookie");
|
||||
if (cookies != null && !cookies.isEmpty()) {
|
||||
for (String cookie : new TextStringBuilder().appendWithSeparators(cookies, ";").build().split(";")) {
|
||||
String[] split = cookie.trim().split("=", 2);
|
||||
String name = split[0];
|
||||
String value = split[1];
|
||||
if ("auth".equals(name)) {
|
||||
return Optional.of(new CookieAuthentication(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<String> authorization = requestHeaders.get("Authorization");
|
||||
if (Verify.isEmpty(authorization)) return Optional.empty();
|
||||
|
||||
|
@ -338,7 +338,9 @@ public class ResponseFactory {
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.HTML)
|
||||
.setContent("<h1>403 Forbidden</h1>" +
|
||||
"<p>You have too many failed login attempts. Please wait 2 minutes until attempting again.</p>")
|
||||
"<p>You have too many failed login attempts. Please wait 2 minutes until attempting again.</p>" +
|
||||
"<script>setTimeout(() => location.reload(), 120500);\" +\n" +
|
||||
"</script>")
|
||||
.setStatus(403)
|
||||
.build();
|
||||
}
|
||||
@ -381,4 +383,20 @@ public class ResponseFactory {
|
||||
return forInternalError(e, "Failed to generate player page");
|
||||
}
|
||||
}
|
||||
|
||||
public Response loginPageResponse() {
|
||||
try {
|
||||
return forPage(pageFactory.loginPage());
|
||||
} catch (IOException e) {
|
||||
return forInternalError(e, "Failed to generate player page");
|
||||
}
|
||||
}
|
||||
|
||||
public Response registerPageResponse() {
|
||||
try {
|
||||
return forPage(pageFactory.registerPage());
|
||||
} catch (IOException e) {
|
||||
return forInternalError(e, "Failed to generate player page");
|
||||
}
|
||||
}
|
||||
}
|
@ -25,8 +25,11 @@ import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.NotFoundException;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.FailReason;
|
||||
import com.djrapitops.plan.delivery.webserver.resolver.*;
|
||||
import com.djrapitops.plan.delivery.webserver.resolver.auth.*;
|
||||
import com.djrapitops.plan.delivery.webserver.resolver.json.RootJSONResolver;
|
||||
import com.djrapitops.plan.exceptions.WebUserAuthException;
|
||||
import com.djrapitops.plan.exceptions.connection.ForbiddenException;
|
||||
import com.djrapitops.plugin.logging.L;
|
||||
import com.djrapitops.plugin.logging.error.ErrorHandler;
|
||||
@ -56,6 +59,11 @@ public class ResponseResolver {
|
||||
private final RootPageResolver rootPageResolver;
|
||||
private final RootJSONResolver rootJSONResolver;
|
||||
private final StaticResourceResolver staticResourceResolver;
|
||||
private LoginPageResolver loginPageResolver;
|
||||
private RegisterPageResolver registerPageResolver;
|
||||
private LoginResolver loginResolver;
|
||||
private LogoutResolver logoutResolver;
|
||||
private RegisterResolver registerResolver;
|
||||
private final ErrorHandler errorHandler;
|
||||
|
||||
private final ResolverService resolverService;
|
||||
@ -76,6 +84,12 @@ public class ResponseResolver {
|
||||
RootJSONResolver rootJSONResolver,
|
||||
StaticResourceResolver staticResourceResolver,
|
||||
|
||||
LoginPageResolver loginPageResolver,
|
||||
RegisterPageResolver registerPageResolver,
|
||||
LoginResolver loginResolver,
|
||||
LogoutResolver logoutResolver,
|
||||
RegisterResolver registerResolver,
|
||||
|
||||
ErrorHandler errorHandler
|
||||
) {
|
||||
this.resolverService = resolverService;
|
||||
@ -88,6 +102,11 @@ public class ResponseResolver {
|
||||
this.rootPageResolver = rootPageResolver;
|
||||
this.rootJSONResolver = rootJSONResolver;
|
||||
this.staticResourceResolver = staticResourceResolver;
|
||||
this.loginPageResolver = loginPageResolver;
|
||||
this.registerPageResolver = registerPageResolver;
|
||||
this.loginResolver = loginResolver;
|
||||
this.logoutResolver = logoutResolver;
|
||||
this.registerResolver = registerResolver;
|
||||
this.errorHandler = errorHandler;
|
||||
}
|
||||
|
||||
@ -99,6 +118,13 @@ public class ResponseResolver {
|
||||
resolverService.registerResolver(plugin, "/favicon.ico", (NoAuthResolver) request -> Optional.of(responseFactory.faviconResponse()));
|
||||
resolverService.registerResolver(plugin, "/network", serverPageResolver);
|
||||
resolverService.registerResolver(plugin, "/server", serverPageResolver);
|
||||
|
||||
resolverService.registerResolver(plugin, "/login", loginPageResolver);
|
||||
resolverService.registerResolver(plugin, "/register", registerPageResolver);
|
||||
resolverService.registerResolver(plugin, "/auth/login", loginResolver);
|
||||
resolverService.registerResolver(plugin, "/auth/logout", logoutResolver);
|
||||
resolverService.registerResolver(plugin, "/auth/register", registerResolver);
|
||||
|
||||
resolverService.registerResolverForMatches(plugin, Pattern.compile("^/$"), rootPageResolver);
|
||||
resolverService.registerResolverForMatches(plugin, Pattern.compile("^.*/(vendor|css|js|img)/.*"), staticResourceResolver);
|
||||
|
||||
@ -114,6 +140,8 @@ public class ResponseResolver {
|
||||
return responseFactory.forbidden403(e.getMessage());
|
||||
} catch (BadRequestException e) {
|
||||
return responseFactory.badRequest(e.getMessage(), request.getPath().asString());
|
||||
} catch (WebUserAuthException e) {
|
||||
throw e; // Pass along
|
||||
} catch (Exception e) {
|
||||
errorHandler.log(L.ERROR, this.getClass(), e);
|
||||
return responseFactory.internalErrorResponse(e, request.getPath().asString());
|
||||
@ -141,7 +169,7 @@ public class ResponseResolver {
|
||||
if (isAuthRequired) {
|
||||
if (!user.isPresent()) {
|
||||
if (webServer.get().isUsingHTTPS()) {
|
||||
return responseFactory.basicAuth();
|
||||
throw new WebUserAuthException(FailReason.NO_USER_PRESENT);
|
||||
} else {
|
||||
return responseFactory.forbidden403();
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
package com.djrapitops.plan.delivery.webserver;
|
||||
|
||||
import com.djrapitops.plan.SubSystem;
|
||||
import com.djrapitops.plan.delivery.web.ResourceService;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONCache;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@ -45,6 +46,13 @@ public class WebServerSystem implements SubSystem {
|
||||
@Override
|
||||
public void enable() {
|
||||
webServer.enable();
|
||||
if (!webServer.isAuthRequired()) {
|
||||
ResourceService.getInstance().addStylesToResource("Plan", "error.html", ResourceService.Position.PRE_CONTENT, "./css/noauth.css");
|
||||
ResourceService.getInstance().addStylesToResource("Plan", "server.html", ResourceService.Position.PRE_CONTENT, "../css/noauth.css");
|
||||
ResourceService.getInstance().addStylesToResource("Plan", "player.html", ResourceService.Position.PRE_CONTENT, "../css/noauth.css");
|
||||
ResourceService.getInstance().addStylesToResource("Plan", "players.html", ResourceService.Position.PRE_CONTENT, "./css/noauth.css");
|
||||
ResourceService.getInstance().addStylesToResource("Plan", "network.html", ResourceService.Position.PRE_CONTENT, "./css/noauth.css");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver.auth;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.WebUser;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ActiveCookieStore {
|
||||
|
||||
private static final Map<String, WebUser> USERS_BY_COOKIE = new HashMap<>();
|
||||
|
||||
public static Optional<WebUser> checkCookie(String cookie) {
|
||||
return Optional.ofNullable(USERS_BY_COOKIE.get(cookie));
|
||||
}
|
||||
|
||||
public static String generateNewCookie(WebUser user) {
|
||||
String cookie = DigestUtils.sha256Hex(user.getName() + UUID.randomUUID() + System.currentTimeMillis());
|
||||
USERS_BY_COOKIE.put(cookie, user);
|
||||
return cookie;
|
||||
}
|
||||
|
||||
public static void removeCookie(String cookie) {
|
||||
USERS_BY_COOKIE.remove(cookie);
|
||||
}
|
||||
|
||||
public static void removeCookie(WebUser user) {
|
||||
USERS_BY_COOKIE.entrySet().stream().filter(entry -> entry.getValue().getName().equals(user.getName()))
|
||||
.findAny()
|
||||
.map(Map.Entry::getKey)
|
||||
.ifPresent(ActiveCookieStore::removeCookie);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver.auth;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.WebUser;
|
||||
|
||||
public class CookieAuthentication implements Authentication {
|
||||
|
||||
private final String cookie;
|
||||
|
||||
public CookieAuthentication(String cookie) {
|
||||
this.cookie = cookie;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebUser getWebUser() {
|
||||
return ActiveCookieStore.checkCookie(cookie).orElse(null);
|
||||
}
|
||||
}
|
@ -25,6 +25,8 @@ import com.djrapitops.plan.settings.locale.lang.Lang;
|
||||
* @see com.djrapitops.plan.exceptions.WebUserAuthException
|
||||
*/
|
||||
public enum FailReason implements Lang {
|
||||
NO_USER_PRESENT("User cookie not present"),
|
||||
EXPIRED_COOKIE("User cookie has expired"),
|
||||
USER_AND_PASS_NOT_SPECIFIED("User and Password not specified"),
|
||||
USER_DOES_NOT_EXIST("User does not exist"),
|
||||
USER_PASS_MISMATCH("User and Password did not match"),
|
||||
|
@ -0,0 +1,46 @@
|
||||
package com.djrapitops.plan.delivery.webserver.auth;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.WebUser;
|
||||
import com.djrapitops.plan.utilities.PassEncryptUtil;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class RegistrationBin {
|
||||
|
||||
private static final Map<String, AwaitingForRegistration> REGISTRATION_BIN = new HashMap<>();
|
||||
|
||||
public static String addInfoForRegistration(String username, String password) throws PassEncryptUtil.CannotPerformOperationException {
|
||||
String hash = PassEncryptUtil.createHash(password);
|
||||
String code = DigestUtils.sha256Hex(username + password + System.currentTimeMillis()).substring(0, 12);
|
||||
REGISTRATION_BIN.put(code, new AwaitingForRegistration(username, hash));
|
||||
return code;
|
||||
}
|
||||
|
||||
public static Optional<WebUser> register(String code, int permissionLevel) {
|
||||
AwaitingForRegistration found = REGISTRATION_BIN.get(code);
|
||||
if (found == null) return Optional.empty();
|
||||
REGISTRATION_BIN.remove(code);
|
||||
return Optional.of(found.toWebUser(permissionLevel));
|
||||
}
|
||||
|
||||
public static boolean contains(String code) {
|
||||
return REGISTRATION_BIN.containsKey(code);
|
||||
}
|
||||
|
||||
private static class AwaitingForRegistration {
|
||||
private final String username;
|
||||
private final String passwordHash;
|
||||
|
||||
public AwaitingForRegistration(String username, String passwordHash) {
|
||||
this.username = username;
|
||||
this.passwordHash = passwordHash;
|
||||
}
|
||||
|
||||
public WebUser toWebUser(int permissionLevel) {
|
||||
return new WebUser(username, passwordHash, permissionLevel);
|
||||
}
|
||||
}
|
||||
}
|
@ -23,6 +23,8 @@ import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
import com.djrapitops.plan.delivery.webserver.ResponseFactory;
|
||||
import com.djrapitops.plan.delivery.webserver.WebServer;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.FailReason;
|
||||
import com.djrapitops.plan.exceptions.WebUserAuthException;
|
||||
import com.djrapitops.plan.identification.Server;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import dagger.Lazy;
|
||||
@ -62,12 +64,8 @@ public class RootPageResolver implements NoAuthResolver {
|
||||
return responseFactory.redirectResponse(redirectTo);
|
||||
}
|
||||
|
||||
Optional<WebUser> webUser = request.getUser();
|
||||
if (!webUser.isPresent()) {
|
||||
return responseFactory.basicAuth();
|
||||
}
|
||||
|
||||
WebUser user = webUser.get();
|
||||
WebUser user = request.getUser()
|
||||
.orElseThrow(() -> new WebUserAuthException(FailReason.NO_USER_PRESENT));
|
||||
|
||||
if (user.hasPermission("page.server")) {
|
||||
return responseFactory.redirectResponse(server.isProxy() ? "network" : "server/" + Html.encodeToURL(server.getIdentifiableName()));
|
||||
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver.resolver.auth;
|
||||
|
||||
import com.djrapitops.plan.delivery.web.resolver.NoAuthResolver;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
import com.djrapitops.plan.delivery.webserver.ResponseFactory;
|
||||
import com.djrapitops.plan.delivery.webserver.WebServer;
|
||||
import dagger.Lazy;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
|
||||
@Singleton
|
||||
public class LoginPageResolver implements NoAuthResolver {
|
||||
|
||||
private ResponseFactory responseFactory;
|
||||
private Lazy<WebServer> webServer;
|
||||
|
||||
@Inject
|
||||
public LoginPageResolver(
|
||||
ResponseFactory responseFactory,
|
||||
Lazy<WebServer> webServer
|
||||
) {
|
||||
this.responseFactory = responseFactory;
|
||||
this.webServer = webServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Response> resolve(Request request) {
|
||||
Optional<WebUser> user = request.getUser();
|
||||
if (user.isPresent() || !webServer.get().isAuthRequired()) {
|
||||
Optional<String> from = request.getQuery().get("from");
|
||||
return Optional.of(responseFactory.redirectResponse(from.orElse("/")));
|
||||
}
|
||||
return Optional.of(responseFactory.loginPageResponse());
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver.resolver.auth;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.WebUser;
|
||||
import com.djrapitops.plan.delivery.web.resolver.NoAuthResolver;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.URIQuery;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.ActiveCookieStore;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.FailReason;
|
||||
import com.djrapitops.plan.exceptions.PassEncryptException;
|
||||
import com.djrapitops.plan.exceptions.WebUserAuthException;
|
||||
import com.djrapitops.plan.exceptions.database.DBOpException;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries;
|
||||
import com.djrapitops.plan.utilities.PassEncryptUtil;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Singleton
|
||||
public class LoginResolver implements NoAuthResolver {
|
||||
|
||||
private DBSystem dbSystem;
|
||||
|
||||
@Inject
|
||||
public LoginResolver(
|
||||
DBSystem dbSystem
|
||||
) {
|
||||
this.dbSystem = dbSystem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Response> resolve(Request request) {
|
||||
try {
|
||||
String cookie = ActiveCookieStore.generateNewCookie(getWebUser(request));
|
||||
return Optional.of(getResponse(cookie));
|
||||
} catch (DBOpException | PassEncryptException e) {
|
||||
throw new WebUserAuthException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Response getResponse(String cookie) {
|
||||
return Response.builder()
|
||||
.setStatus(200)
|
||||
.setHeader("Set-Cookie", "auth=" + cookie + "; Path=/; Max-Age=" + TimeUnit.HOURS.toSeconds(2L))
|
||||
.setJSONContent(Collections.singletonMap("success", true))
|
||||
.build();
|
||||
}
|
||||
|
||||
public WebUser getWebUser(Request request) throws PassEncryptUtil.CannotPerformOperationException, PassEncryptUtil.InvalidHashException {
|
||||
URIQuery query = request.getQuery();
|
||||
String username = query.get("user").orElseThrow(() -> new BadRequestException("'user' parameter not defined"));
|
||||
String password = query.get("password").orElseThrow(() -> new BadRequestException("'password' parameter not defined"));
|
||||
WebUser webUser = dbSystem.getDatabase().query(WebUserQueries.fetchWebUser(username))
|
||||
.orElseThrow(() -> new BadRequestException(FailReason.USER_DOES_NOT_EXIST.getReason() + ": " + username));
|
||||
|
||||
boolean correctPass = PassEncryptUtil.verifyPassword(password, webUser.getSaltedPassHash());
|
||||
if (!correctPass) {
|
||||
throw new WebUserAuthException(FailReason.USER_PASS_MISMATCH);
|
||||
}
|
||||
return webUser;
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver.resolver.auth;
|
||||
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.NoAuthResolver;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.ActiveCookieStore;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.FailReason;
|
||||
import com.djrapitops.plan.exceptions.WebUserAuthException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
|
||||
@Singleton
|
||||
public class LogoutResolver implements NoAuthResolver {
|
||||
|
||||
@Inject
|
||||
public LogoutResolver() {}
|
||||
|
||||
@Override
|
||||
public Optional<Response> resolve(Request request) {
|
||||
String cookies = request.getHeader("Cookie").orElse("");
|
||||
String foundCookie = null;
|
||||
for (String cookie : cookies.split(";")) {
|
||||
if (cookie.isEmpty()) continue;
|
||||
String[] split = cookie.split("=");
|
||||
String name = split[0];
|
||||
String value = split[1];
|
||||
if ("auth".equals(name)) {
|
||||
foundCookie = value;
|
||||
ActiveCookieStore.removeCookie(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (foundCookie == null) {
|
||||
throw new WebUserAuthException(FailReason.NO_USER_PRESENT);
|
||||
}
|
||||
return Optional.of(getResponse(foundCookie));
|
||||
}
|
||||
|
||||
public Response getResponse(String cookie) {
|
||||
return Response.builder()
|
||||
.setStatus(200)
|
||||
.setHeader("Set-Cookie", "auth=" + cookie + "; Max-Age=1")
|
||||
.setMimeType(MimeType.HTML)
|
||||
.setContent(
|
||||
"<p>Logging out..</p><script>const urlParams = new URLSearchParams(window.location.search);" +
|
||||
"const cause = urlParams.get('cause');" +
|
||||
"setTimeout(() => window.location.href = cause ? '../login?cause=' + cause : '../login', 1000);" +
|
||||
"</script>"
|
||||
)
|
||||
.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver.resolver.auth;
|
||||
|
||||
import com.djrapitops.plan.delivery.web.resolver.NoAuthResolver;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
import com.djrapitops.plan.delivery.webserver.ResponseFactory;
|
||||
import com.djrapitops.plan.delivery.webserver.WebServer;
|
||||
import dagger.Lazy;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
|
||||
@Singleton
|
||||
public class RegisterPageResolver implements NoAuthResolver {
|
||||
|
||||
private ResponseFactory responseFactory;
|
||||
private Lazy<WebServer> webServer;
|
||||
|
||||
@Inject
|
||||
public RegisterPageResolver(
|
||||
ResponseFactory responseFactory,
|
||||
Lazy<WebServer> webServer
|
||||
) {
|
||||
this.responseFactory = responseFactory;
|
||||
this.webServer = webServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Response> resolve(Request request) {
|
||||
Optional<WebUser> user = request.getUser();
|
||||
if (user.isPresent() || !webServer.get().isAuthRequired()) {
|
||||
return Optional.of(responseFactory.redirectResponse("/"));
|
||||
}
|
||||
return Optional.of(responseFactory.registerPageResponse());
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver.resolver.auth;
|
||||
|
||||
import com.djrapitops.plan.delivery.web.resolver.NoAuthResolver;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.URIQuery;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.RegistrationBin;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries;
|
||||
import com.djrapitops.plan.utilities.PassEncryptUtil;
|
||||
import com.djrapitops.plan.utilities.java.Maps;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
@Singleton
|
||||
public class RegisterResolver implements NoAuthResolver {
|
||||
|
||||
private DBSystem dbSystem;
|
||||
|
||||
@Inject
|
||||
public RegisterResolver(DBSystem dbSystem) {this.dbSystem = dbSystem;}
|
||||
|
||||
@Override
|
||||
public Optional<Response> resolve(Request request) {
|
||||
return Optional.of(getResponse(request));
|
||||
}
|
||||
|
||||
public Response getResponse(Request request) {
|
||||
URIQuery query = request.getQuery();
|
||||
Optional<String> checkCode = query.get("code");
|
||||
if (checkCode.isPresent()) {
|
||||
return Response.builder()
|
||||
.setStatus(200)
|
||||
.setJSONContent(Collections.singletonMap("success", !RegistrationBin.contains(checkCode.get())))
|
||||
.build();
|
||||
}
|
||||
|
||||
String username = query.get("user").orElseThrow(() -> new BadRequestException("'user' parameter not defined"));
|
||||
|
||||
boolean alreadyExists = dbSystem.getDatabase().query(WebUserQueries.fetchWebUser(username)).isPresent();
|
||||
if (alreadyExists) throw new BadRequestException("User '" + username + "' already exists!");
|
||||
|
||||
String password = query.get("password").orElseThrow(() -> new BadRequestException("'password' parameter not defined"));
|
||||
try {
|
||||
String code = RegistrationBin.addInfoForRegistration(username, password);
|
||||
return Response.builder()
|
||||
.setStatus(200)
|
||||
.setJSONContent(Maps.builder(String.class, Object.class)
|
||||
.put("success", true)
|
||||
.put("code", code)
|
||||
.build())
|
||||
.build();
|
||||
} catch (PassEncryptUtil.CannotPerformOperationException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +1,19 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.modules;
|
||||
|
||||
import com.djrapitops.plan.placeholder.*;
|
||||
|
@ -1,3 +1,19 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.placeholder;
|
||||
|
||||
public interface Placeholders {
|
||||
|
@ -0,0 +1,3 @@
|
||||
#logout-button {
|
||||
display: none;
|
||||
}
|
@ -8604,6 +8604,10 @@ button.bg-dark:focus {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.w-15 {
|
||||
width: 15% !important;
|
||||
}
|
||||
|
||||
.w-22 {
|
||||
width: 22% !important;
|
||||
}
|
||||
@ -11707,6 +11711,7 @@ a:focus {
|
||||
|
||||
.hidden {
|
||||
width: 0 !important;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hidden .sidebar .sidebar-brand img {
|
||||
|
@ -755,6 +755,10 @@ div.scrollbar {
|
||||
background-color: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.bg-gradient {
|
||||
background: rgba(0, 0, 0, 0) linear-gradient(180deg, rgba(0, 0, 0, 0) 20%, rgba(0, 0, 0, 0.2) 100%);
|
||||
}
|
||||
|
||||
.bg-red {
|
||||
background-color: #F44336;
|
||||
color: #fff;
|
||||
|
@ -32,7 +32,7 @@
|
||||
|
||||
<!-- Sidebar - Brand -->
|
||||
<a class="sidebar-brand d-flex align-items-center justify-content-center">
|
||||
<img class="w-22" src="img/Flaticon_circle.png">
|
||||
<img alt="logo" class="w-22" src="img/Flaticon_circle.png">
|
||||
</a>
|
||||
|
||||
<!-- Divider -->
|
||||
@ -54,6 +54,9 @@
|
||||
<button class="btn bg-plan" data-target="#informationModal" data-toggle="modal" type="button">
|
||||
<i class="fa fa-fw fa-question-circle"></i>
|
||||
</button>
|
||||
<a class="btn bg-plan" href="/auth/logout" id="logout-button">
|
||||
<i class="fa fa-fw fa-door-open"></i> Logout
|
||||
</a>
|
||||
</div>
|
||||
<div class="ml-3 align-items-center justify-content-between">
|
||||
${version}
|
||||
|
@ -1,6 +1,6 @@
|
||||
(function ($) {
|
||||
var bgElements = ['.sidebar', '.btn'];
|
||||
var textElements = [];
|
||||
var bgElements = ['.sidebar', '.btn', 'body'];
|
||||
var textElements = ['a', 'button'];
|
||||
|
||||
var colors = ['plan',
|
||||
'red', 'pink', 'purple', 'deep-purple',
|
||||
|
@ -0,0 +1,71 @@
|
||||
function drawSine(canvasId) {
|
||||
// https://gist.github.com/gkhays/e264009c0832c73d5345847e673a64ab
|
||||
function drawPoint(ctx, x, y) {
|
||||
var radius = 2;
|
||||
ctx.beginPath();
|
||||
|
||||
// Hold x constant at 4 so the point only moves up and down.
|
||||
ctx.arc(x - 5, y, radius, 0, 2 * Math.PI, false);
|
||||
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.fill();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function plotSine(ctx, xOffset, yOffset) {
|
||||
var width = ctx.canvas.width;
|
||||
var height = ctx.canvas.height;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeStyle = "#fff";
|
||||
|
||||
// console.log("Drawing point...");
|
||||
|
||||
var x = -2;
|
||||
var y = 0;
|
||||
var amplitude = 50;
|
||||
var frequency = 50;
|
||||
// ctx.moveTo(x, y);
|
||||
ctx.moveTo(x, 50);
|
||||
while (x <= width) {
|
||||
y = height / 2 + amplitude * Math.sin((x + xOffset) / frequency) * Math.cos((x + xOffset) / (frequency * 0.54515978463));
|
||||
ctx.lineTo(x, y);
|
||||
x += 5;
|
||||
// console.log("x="+x+" y="+y);
|
||||
}
|
||||
ctx.stroke();
|
||||
ctx.save();
|
||||
drawPoint(ctx, x, y);
|
||||
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function draw() {
|
||||
var canvas = document.getElementById(canvasId);
|
||||
var context = canvas.getContext("2d");
|
||||
|
||||
context.clearRect(0, 0, 1000, 150);
|
||||
context.save();
|
||||
|
||||
plotSine(context, step, 100);
|
||||
context.restore();
|
||||
|
||||
step += 0.5;
|
||||
window.requestAnimationFrame(draw);
|
||||
}
|
||||
|
||||
function fix_dpi() {
|
||||
var canvas = document.getElementById(canvasId);
|
||||
let dpi = window.devicePixelRatio;//get canvas
|
||||
let ctx = canvas.getContext('2d');
|
||||
let style_width = +getComputedStyle(canvas).getPropertyValue("width").slice(0, -2);//scale the canvascanvas.setAttribute('height', style_height * dpi);
|
||||
canvas.setAttribute('width', style_width * dpi);
|
||||
}
|
||||
|
||||
fix_dpi();
|
||||
var step = -1;
|
||||
window.requestAnimationFrame(draw);
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
function jsonRequest(address, callback) {
|
||||
setTimeout(function () {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.withCredentials = true;
|
||||
xhttp.onreadystatechange = function () {
|
||||
if (this.readyState === 4) {
|
||||
try {
|
||||
@ -17,9 +18,10 @@ function jsonRequest(address, callback) {
|
||||
} else if (this.status === 404 || this.status === 403 || this.status === 500) {
|
||||
callback(null, "HTTP " + this.status + " (See " + address + ")")
|
||||
} else if (this.status === 400) {
|
||||
callback(null, this.responseText + " (See " + address + ")")
|
||||
const json = JSON.parse(this.responseText);
|
||||
callback(json, json.error)
|
||||
} else if (this.status === 0) {
|
||||
callback(null, "Request was blocked. (Adblocker maybe?)")
|
||||
callback(null, "Request did not reach the server. (Server offline / Adblocker?)")
|
||||
}
|
||||
} catch (e) {
|
||||
callback(null, e.message + " (See " + address + ")")
|
||||
|
230
Plan/common/src/main/resources/assets/plan/web/login.html
Normal file
230
Plan/common/src/main/resources/assets/plan/web/login.html
Normal file
@ -0,0 +1,230 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta content="IE=edge" http-equiv="X-UA-Compatible">
|
||||
<meta content="width=device-width, initial-scale=1, shrink-to-fit=no" name="viewport">
|
||||
<meta content="Player Analytics, login page" name="description">
|
||||
<meta content="Rsl1122" name="author">
|
||||
|
||||
<title>Plan | Login</title>
|
||||
|
||||
<!-- Custom fonts for this template-->
|
||||
<link href="vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
|
||||
<link crossorigin="anonymous" href="https://fonts.googleapis.com/css?family=Nunito:300,400,600,700,800,900"
|
||||
rel="stylesheet">
|
||||
|
||||
<!-- Custom styles for this template-->
|
||||
<link href="css/sb-admin-2.css" rel="stylesheet">
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
|
||||
</head>
|
||||
|
||||
<body class="bg-plan bg-gradient">
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- Outer Row -->
|
||||
<div class="row justify-content-center">
|
||||
<img alt="logo" class="w-15 mt-5" src="img/Flaticon_circle.png">
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-xl-6 col-lg-7 col-md-9">
|
||||
<div class="card o-hidden border-0 shadow-lg my-5">
|
||||
<div class="card-body p-0">
|
||||
<!-- Nested Row within Card Body -->
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="p-5">
|
||||
<div class="hidden alert alert-danger" id="fail-msg">
|
||||
Failed
|
||||
</div>
|
||||
<form class="user">
|
||||
<div class="form-group">
|
||||
<input autocomplete="username" class="form-control form-control-user"
|
||||
id="inputUser"
|
||||
placeholder="Username" type="text">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input autocomplete="current-password" class="form-control form-control-user"
|
||||
id="inputPassword" placeholder="Password"
|
||||
type="password">
|
||||
</div>
|
||||
<a class="btn bg-plan btn-user btn-block" href="#" id="login-button">
|
||||
Login
|
||||
</a>
|
||||
</form>
|
||||
<hr>
|
||||
<div class="text-center">
|
||||
<a class="col-plan small" href="#" id="forgot-button">Forgot Password?</a>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<a class="col-plan small" href="./register">Create an Account!</a>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<button class="btn col-plan" data-target="#colorChooserModal" data-toggle="modal"
|
||||
type="button">
|
||||
<i class="fa fa-palette"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<canvas class="col-xl-3 col-lg-3 col-md-5" id="decoration" style="height: 100px"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Forgot Password Modal -->
|
||||
<div aria-hidden="true" aria-labelledby="forgotModalLabel" class="modal fade" id="forgotModal"
|
||||
role="dialog" tabindex="-1">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-white">
|
||||
<h5 class="modal-title" id="forgotModalLabel"><i class="far fa-fw fa-hand-point-right"></i>
|
||||
Forgot password? Unregister and register again.
|
||||
</h5>
|
||||
<button aria-label="Close" class="close" data-dismiss="modal" type="button">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body bg-white">
|
||||
<p>Use the following command in game to remove your current user:</p>
|
||||
<p><code>/${command} unregister</code></p>
|
||||
<p>Or using console:</p>
|
||||
<p><code>${command} unregister [username]</code></p>
|
||||
<p>After using the command, <a class="col-plan" href="./register">Create an Account!</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Color Chooser Modal -->
|
||||
<div aria-hidden="true" aria-labelledby="colorChooserModalLabel" class="modal fade" id="colorChooserModal"
|
||||
role="dialog" tabindex="-1">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="colorChooserModalLabel"><i class="fa fa-palette"></i> Theme Select
|
||||
</h5>
|
||||
<button aria-label="Close" class="close" data-dismiss="modal" type="button">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<button class="btn color-chooser" id="choose-plan" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-red" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-pink" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-purple" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-deep-purple" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-indigo" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-blue" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-light-blue" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-cyan" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-teal" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-green" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-light-green" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-lime" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-yellow" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-amber" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-orange" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-deep-orange" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-brown" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-grey" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-blue-grey" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" id="night-mode-toggle" type="button"><i class="fa fa-fw fa-cloud-moon"></i>
|
||||
Night
|
||||
Mode
|
||||
</button>
|
||||
<button class="btn bg-plan" data-dismiss="modal" type="button">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap core JavaScript-->
|
||||
<script src="vendor/jquery/jquery.min.js"></script>
|
||||
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<!-- Core plugin JavaScript-->
|
||||
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
|
||||
|
||||
<!-- Custom scripts for all pages-->
|
||||
<script src="js/sb-admin-2.min.js"></script>
|
||||
<script src="js/xmlhttprequests.js"></script>
|
||||
<script src="js/color-selector.js"></script>
|
||||
|
||||
<script src="js/logonsine.js"></script>
|
||||
<script id="mainScript">
|
||||
drawSine("decoration");
|
||||
|
||||
const msg = $(`#fail-msg`);
|
||||
|
||||
function displayError(message) {
|
||||
msg.text(message);
|
||||
msg.removeClass('hidden');
|
||||
}
|
||||
|
||||
$('#forgot-button').click(() => $('#forgotModal').modal());
|
||||
$('#login-button').click(() => {
|
||||
msg.addClass('hidden');
|
||||
const user = $('#inputUser').val();
|
||||
if (!user || user.length < 1) {
|
||||
return displayError('You need to specify a Username');
|
||||
}
|
||||
if (user.length > 50) {
|
||||
return displayError('Username can be up to 50 characters, yours is ' + user.length);
|
||||
}
|
||||
const password = $('#inputPassword').val();
|
||||
if (!password || password.length < 1) {
|
||||
return displayError('You need to specify a Password');
|
||||
}
|
||||
jsonRequest(`./auth/login?user=${encodeURI(user)}&password=${encodeURI(password)}`, (json, error) => {
|
||||
if (error) {
|
||||
if (error.includes("HTTP 403")) {
|
||||
location.reload();
|
||||
}
|
||||
$('#inputPassword').val("");
|
||||
return displayError('Login failed: ' + error);
|
||||
}
|
||||
if (json && json.success) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const cameFrom = urlParams.get('from');
|
||||
window.location.href = cameFrom ? cameFrom : './';
|
||||
} else {
|
||||
return displayError('Login failed: ' + json.error);
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -41,7 +41,7 @@
|
||||
|
||||
<!-- Sidebar - Brand -->
|
||||
<a class="sidebar-brand d-flex align-items-center justify-content-center">
|
||||
<img class="w-22" src="./img/Flaticon_circle.png">
|
||||
<img alt="logo" class="w-22" src="./img/Flaticon_circle.png">
|
||||
</a>
|
||||
|
||||
<!-- Divider -->
|
||||
@ -120,6 +120,9 @@
|
||||
<button class="btn bg-plan" data-target="#informationModal" data-toggle="modal" type="button">
|
||||
<i class="fa fa-fw fa-question-circle"></i>
|
||||
</button>
|
||||
<a class="btn bg-plan" href="/auth/logout" id="logout-button">
|
||||
<i class="fa fa-fw fa-door-open"></i> Logout
|
||||
</a>
|
||||
</div>
|
||||
<div class="ml-3 align-items-center justify-content-between">
|
||||
${version}
|
||||
|
@ -41,7 +41,7 @@
|
||||
|
||||
<!-- Sidebar - Brand -->
|
||||
<a class="sidebar-brand d-flex align-items-center justify-content-center">
|
||||
<img class="w-22" src="../img/Flaticon_circle.png">
|
||||
<img alt="logo" class="w-22" src="../img/Flaticon_circle.png">
|
||||
</a>
|
||||
|
||||
<!-- Divider -->
|
||||
@ -108,6 +108,9 @@
|
||||
<button class="btn bg-plan" data-target="#informationModal" data-toggle="modal" type="button">
|
||||
<i class="fa fa-fw fa-question-circle"></i>
|
||||
</button>
|
||||
<a class="btn bg-plan" href="/auth/logout" id="logout-button">
|
||||
<i class="fa fa-fw fa-door-open"></i> Logout
|
||||
</a>
|
||||
</div>
|
||||
<div class="ml-3 align-items-center justify-content-between">
|
||||
${version}
|
||||
|
@ -32,7 +32,7 @@
|
||||
|
||||
<!-- Sidebar - Brand -->
|
||||
<a class="sidebar-brand d-flex align-items-center justify-content-center">
|
||||
<img class="w-22" src="img/Flaticon_circle.png">
|
||||
<img alt="logo" class="w-22" src="img/Flaticon_circle.png">
|
||||
</a>
|
||||
|
||||
<!-- Divider -->
|
||||
@ -54,6 +54,9 @@
|
||||
<button class="btn bg-plan" data-target="#informationModal" data-toggle="modal" type="button">
|
||||
<i class="fa fa-fw fa-question-circle"></i>
|
||||
</button>
|
||||
<a class="btn bg-plan" href="/auth/logout" id="logout-button">
|
||||
<i class="fa fa-fw fa-door-open"></i> Logout
|
||||
</a>
|
||||
</div>
|
||||
<div class="ml-3 align-items-center justify-content-between">
|
||||
${version}
|
||||
|
236
Plan/common/src/main/resources/assets/plan/web/register.html
Normal file
236
Plan/common/src/main/resources/assets/plan/web/register.html
Normal file
@ -0,0 +1,236 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta content="IE=edge" http-equiv="X-UA-Compatible">
|
||||
<meta content="width=device-width, initial-scale=1, shrink-to-fit=no" name="viewport">
|
||||
<meta content="Player Analytics, login page" name="description">
|
||||
<meta content="Rsl1122" name="author">
|
||||
|
||||
<title>Plan | Register</title>
|
||||
|
||||
<!-- Custom fonts for this template-->
|
||||
<link href="vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
|
||||
<link crossorigin="anonymous" href="https://fonts.googleapis.com/css?family=Nunito:300,400,600,700,800,900"
|
||||
rel="stylesheet">
|
||||
|
||||
<!-- Custom styles for this template-->
|
||||
<link href="css/sb-admin-2.css" rel="stylesheet">
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
|
||||
</head>
|
||||
|
||||
<body class="bg-plan bg-gradient">
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- Outer Row -->
|
||||
<div class="row justify-content-center">
|
||||
<img alt="logo" class="w-15 mt-5" src="img/Flaticon_circle.png">
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-xl-6 col-lg-7 col-md-9">
|
||||
<div class="card o-hidden border-0 shadow-lg my-5">
|
||||
<div class="card-body p-0">
|
||||
<!-- Nested Row within Card Body -->
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="p-5">
|
||||
<div class="text-center">
|
||||
<h1 class="h4 text-gray-900 mb-4">Create a new user</h1>
|
||||
</div>
|
||||
<div class="hidden alert alert-danger" id="fail-msg">
|
||||
Failed
|
||||
</div>
|
||||
<form class="user">
|
||||
<div class="form-group">
|
||||
<input autocomplete="username" class="form-control form-control-user"
|
||||
id="inputUser"
|
||||
placeholder="Username" type="text">
|
||||
<small class="form-text text-muted" id="usernameHelpBlock">
|
||||
Username can be up to 50 characters.
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input autocomplete="new-password" class="form-control form-control-user"
|
||||
id="inputPassword" placeholder="Password" type="password">
|
||||
<small class="form-text text-muted" id="passwordHelpBlock">
|
||||
Password should be more than 8 characters, but there are no limitations.
|
||||
</small>
|
||||
</div>
|
||||
<a class="btn bg-plan btn-user btn-block" href="#" id="register-button">
|
||||
Register
|
||||
</a>
|
||||
</form>
|
||||
<hr>
|
||||
<div class="text-center">
|
||||
<a class="col-plan small" href="./login">Already have an account? Login!</a>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<button class="btn col-plan" data-target="#colorChooserModal" data-toggle="modal"
|
||||
type="button">
|
||||
<i class="fa fa-palette"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Finalize Register Modal -->
|
||||
<div aria-hidden="true" aria-labelledby="finalizeModalLabel" class="modal fade" id="finalizeModal"
|
||||
role="dialog" tabindex="-1">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-white">
|
||||
<h5 class="modal-title" id="finalizeModalLabel"><i class="far fa-fw fa-hand-point-right"></i>
|
||||
Complete Registration
|
||||
</h5>
|
||||
<button aria-label="Close" class="close" data-dismiss="modal" type="button">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body bg-white">
|
||||
<p>You can now finish registering the user. The registration should be completed before next
|
||||
restart.</p>
|
||||
<p>Use the following command in game to finish registration:</p>
|
||||
<p><code>/${command} register --code <span class="register-code"></span></code></p>
|
||||
<p>Or using console:</p>
|
||||
<p><code>${command} register superuser --code <span class="register-code"></span></code></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Color Chooser Modal -->
|
||||
<div aria-hidden="true" aria-labelledby="colorChooserModalLabel" class="modal fade" id="colorChooserModal"
|
||||
role="dialog" tabindex="-1">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="colorChooserModalLabel"><i class="fa fa-palette"></i> Theme Select
|
||||
</h5>
|
||||
<button aria-label="Close" class="close" data-dismiss="modal" type="button">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<button class="btn color-chooser" id="choose-plan" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-red" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-pink" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-purple" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-deep-purple" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-indigo" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-blue" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-light-blue" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-cyan" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-teal" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-green" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-light-green" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-lime" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-yellow" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-amber" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-orange" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-deep-orange" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-brown" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-grey" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
<button class="btn color-chooser" id="choose-blue-grey" type="button"><i
|
||||
class="fa fa-palette"></i></button>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" id="night-mode-toggle" type="button"><i class="fa fa-fw fa-cloud-moon"></i>
|
||||
Night
|
||||
Mode
|
||||
</button>
|
||||
<button class="btn bg-plan" data-dismiss="modal" type="button">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap core JavaScript-->
|
||||
<script src="vendor/jquery/jquery.min.js"></script>
|
||||
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<!-- Core plugin JavaScript-->
|
||||
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
|
||||
|
||||
<!-- Custom scripts for all pages-->
|
||||
<script src="js/sb-admin-2.min.js"></script>
|
||||
<script src="js/xmlhttprequests.js"></script>
|
||||
<script src="js/color-selector.js"></script>
|
||||
|
||||
<script id="mainScript">
|
||||
const msg = $(`#fail-msg`);
|
||||
|
||||
function displayError(message) {
|
||||
msg.text(message);
|
||||
msg.removeClass('hidden');
|
||||
}
|
||||
|
||||
function checkIfRegistered(code) {
|
||||
jsonRequest(`./auth/register?code=${encodeURI(code)}`, (json, error) => {
|
||||
if (error) {
|
||||
displayError('Checking registration status failed: ' + error)
|
||||
}
|
||||
if (json && json.success) {
|
||||
window.location.href = "./login";
|
||||
} else {
|
||||
setTimeout(() => checkIfRegistered(code), 5000);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$('#register-button').click(() => {
|
||||
msg.addClass('hidden');
|
||||
const user = $('#inputUser').val();
|
||||
if (!user || user.length < 1) {
|
||||
return displayError('You need to specify a Username');
|
||||
}
|
||||
if (user.length > 50) {
|
||||
return displayError('Username can be up to 50 characters, yours is ' + user.length);
|
||||
}
|
||||
const password = $('#inputPassword').val();
|
||||
if (!password || password.length < 1) {
|
||||
return displayError('You need to specify a Password');
|
||||
}
|
||||
jsonRequest(`./auth/register?user=${encodeURI(user)}&password=${encodeURI(password)}`, (json, error) => {
|
||||
if (error) {
|
||||
return displayError('Registration failed: ' + error);
|
||||
}
|
||||
const code = json.code;
|
||||
$('.register-code').text(code);
|
||||
$('#finalizeModal').modal();
|
||||
setTimeout(() => checkIfRegistered(code), 10000);
|
||||
});
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -39,7 +39,7 @@
|
||||
|
||||
<!-- Sidebar - Brand -->
|
||||
<a class="sidebar-brand d-flex align-items-center justify-content-center">
|
||||
<img class="w-22" src="../img/Flaticon_circle.png">
|
||||
<img alt="logo" class="w-22" src="../img/Flaticon_circle.png">
|
||||
</a>
|
||||
|
||||
<!-- Divider -->
|
||||
@ -126,6 +126,9 @@
|
||||
<button class="btn bg-plan" data-target="#informationModal" data-toggle="modal" type="button">
|
||||
<i class="fa fa-fw fa-question-circle"></i>
|
||||
</button>
|
||||
<a class="btn bg-plan" href="/auth/logout" id="logout-button">
|
||||
<i class="fa fa-fw fa-door-open"></i> Logout
|
||||
</a>
|
||||
</div>
|
||||
<div class="ml-3 align-items-center justify-content-between">
|
||||
${version}
|
||||
|
Loading…
Reference in New Issue
Block a user