diff --git a/WebContent/adduser.jsp b/WebContent/adduser.jsp new file mode 100644 index 0000000..92e31bf --- /dev/null +++ b/WebContent/adduser.jsp @@ -0,0 +1,134 @@ + +<%-- +Copyright (c) 2014, Andy Janata +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted +provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation and/or other materials provided + with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY +WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--%> +<%-- +Bootstrapper to add first user accounts. All accounts created this way will be root administrator +accounts by default. + +@author Andy Janata (ajanata@socialgamer.net) +--%> +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> +<%@ page import="com.google.inject.Injector" %> +<%@ page import="net.socialgamer.cah.RequestWrapper" %> +<%@ page import="net.socialgamer.cah.StartupUtils" %> +<%@ page import="net.socialgamer.cah.Constants" %> +<%@ page import="net.socialgamer.cah.db.Account" %> +<%@ page import="java.util.Date" %> +<%@ page import="org.hibernate.Session" %> +<%@ page import="org.hibernate.Transaction" %> + +<% +RequestWrapper wrapper = new RequestWrapper(request); +if (!Constants.ADMIN_IP_ADDRESSES.contains(wrapper.getRemoteAddr())) { + response.sendError(403, "Access is restricted to known hosts"); + return; +} + +ServletContext servletContext = pageContext.getServletContext(); +Injector injector = (Injector) servletContext.getAttribute(StartupUtils.INJECTOR); + +String error = ""; +String status = ""; + +final String save = request.getParameter("save"); +final String username = request.getParameter("username"); +final String password1 = request.getParameter("password1"); +final String password2 = request.getParameter("password2"); +final String email = request.getParameter("email"); +if ("save".equals(save) + && null != username && !username.isEmpty() + && null != password1 && !password1.isEmpty() + && null != password2 && !password2.isEmpty() + && password1.equals(password2)) { + final Session s = injector.getInstance(Session.class); + // check for existing account + final Account existingAccount = Account.getAccount(s, username); + if (null != existingAccount) { + error = "Username already exists."; + } else { + final Transaction t = s.beginTransaction(); + t.begin(); + + final Account newAccount = new Account(); + newAccount.setUsername(username); + // FIXME hash password + newAccount.setPassword(password1); + newAccount.setEmail(email); + final Date now = new Date(); + newAccount.setCreated(now); + newAccount.setLastSeen(now); + newAccount.setVerifiedPerson(true); + newAccount.setRoot(true); + try { + s.save(newAccount); + t.commit(); + s.close(); + status = "Created user " + username; + } catch (Exception e) { + error = e.getMessage(); + t.rollback(); + s.close(); + } + } + +} else if ("save".equals(save)) { + error = "Username, password1, and password2 are required. Password must match."; +} + +%> + + + + +PYX - Add Admin Accounts + + +<%= error %> +<%= status %> + +

Passwords are stored in plain-text right now!

+
+ + + + + +
+ + + +
+ + + +
+ + + +
+ + +
+ + diff --git a/WebContent/cah.css b/WebContent/cah.css index 06437b3..a38dc2f 100644 --- a/WebContent/cah.css +++ b/WebContent/cah.css @@ -85,7 +85,7 @@ h2,h3,h4 { #nickbox { border: 1px solid black; - display: inline; + display: inline-block; padding: 5px; } diff --git a/WebContent/game.jsp b/WebContent/game.jsp index d9d4c62..2be8c9f 100644 --- a/WebContent/game.jsp +++ b/WebContent/game.jsp @@ -100,10 +100,18 @@ HttpSession hSession = request.getSession(true); implementing a way for players to manage card sets in the game by themselves.
- Nickname: + + + +
+ Enter your password if you have registered your nickname, otherwise leave it blank. +
+ TODO account registration +

diff --git a/WebContent/js/cah.app.js b/WebContent/js/cah.app.js index 257d512..314d60c 100644 --- a/WebContent/js/cah.app.js +++ b/WebContent/js/cah.app.js @@ -41,7 +41,7 @@ $(document).ready(function() { } $("#nicknameconfirm").click(nicknameconfirm_click); $("#nickbox").keyup(nickbox_keyup); - $("#nickbox").focus(); + $("#nickname").focus(); $(".chat", $("#tab-global")).keyup(chat_keyup($(".chat_submit", $("#tab-global")))); $(".chat_submit", $("#tab-global")).click(chatsubmit_click(null, $("#tab-global"))); @@ -94,11 +94,12 @@ function nickbox_keyup(e) { */ function nicknameconfirm_click() { var nickname = $.trim($("#nickname").val()); + var password = $.trim($("#password").val()); $.cookie("nickname", nickname, { domain : cah.COOKIE_DOMAIN, expires : 365 }); - cah.Ajax.build(cah.$.AjaxOperation.REGISTER).withNickname(nickname).run(); + cah.Ajax.build(cah.$.AjaxOperation.REGISTER).withNickname(nickname).withPassword(password).run(); } /** diff --git a/WebContent/js/cah.constants.js b/WebContent/js/cah.constants.js index f2656d2..4e4fdfa 100644 --- a/WebContent/js/cah.constants.js +++ b/WebContent/js/cah.constants.js @@ -132,6 +132,7 @@ cah.$.ErrorCode.NO_NICK_SPECIFIED = "nns"; cah.$.ErrorCode.NOT_ADMIN = "na"; cah.$.ErrorCode.NOT_YOUR_TURN = "nyt"; cah.$.ErrorCode.BANNED = "B&"; +cah.$.ErrorCode.ACCOUNT_IS_REGISTERED = "air"; cah.$.ErrorCode.INVALID_NICK = "in"; cah.$.ErrorCode.ALREADY_STARTED = "as"; cah.$.ErrorCode.BAD_REQUEST = "br"; @@ -142,6 +143,7 @@ cah.$.ErrorCode.ALREADY_STOPPED = "aS"; cah.$.ErrorCode.NOT_ENOUGH_PLAYERS = "nep"; cah.$.ErrorCode.INVALID_GAME = "ig"; cah.$.ErrorCode.NO_MSG_SPECIFIED = "nms"; +cah.$.ErrorCode.ACCOUNT_NOT_REGISTERED = "anr"; cah.$.ErrorCode.NOT_ENOUGH_CARDS = "nec"; cah.$.ErrorCode_msg = {}; cah.$.ErrorCode_msg['tmg'] = "There are too many games already in progress. Either join an existing game, or wait for one to become available."; @@ -169,6 +171,7 @@ cah.$.ErrorCode_msg['nns'] = "No nickname specified."; cah.$.ErrorCode_msg['ngh'] = "Only the game host can do that."; cah.$.ErrorCode_msg['nec'] = "You must select at least one base card set."; cah.$.ErrorCode_msg['serr'] = "An error occured on the server."; +cah.$.ErrorCode_msg['anr'] = "That nickname is not registered. Please register or do not user a password."; cah.$.ErrorCode_msg['nsu'] = "No such user."; cah.$.ErrorCode_msg['wp'] = "That password is incorrect."; cah.$.ErrorCode_msg['as'] = "The game has already started."; @@ -179,6 +182,7 @@ cah.$.ErrorCode_msg['na'] = "You are not an administrator."; cah.$.ErrorCode_msg['niu'] = "Nickname is already in use."; cah.$.ErrorCode_msg['B&'] = "Banned."; cah.$.ErrorCode_msg['ad'] = "Access denied."; +cah.$.ErrorCode_msg['air'] = "That nickname is registered. Please provide its password."; cah.$.ErrorCode_msg['nj'] = "You are not the judge."; cah.$.GameInfo = function() { diff --git a/src/hibernate.cfg.xml b/src/hibernate.cfg.xml index 79225b4..3f616c8 100644 --- a/src/hibernate.cfg.xml +++ b/src/hibernate.cfg.xml @@ -14,6 +14,7 @@ false false + diff --git a/src/net/socialgamer/cah/Constants.java b/src/net/socialgamer/cah/Constants.java index 2560d56..da1d5c5 100644 --- a/src/net/socialgamer/cah/Constants.java +++ b/src/net/socialgamer/cah/Constants.java @@ -289,6 +289,9 @@ public class Constants { */ public enum ErrorCode implements Localizable { ACCESS_DENIED("ad", "Access denied."), + ACCOUNT_IS_REGISTERED("air", "That nickname is registered. Please provide its password."), + ACCOUNT_NOT_REGISTERED("anr", + "That nickname is not registered. Please register or do not user a password."), ALREADY_STARTED("as", "The game has already started."), ALREADY_STOPPED("aS", "The game has already stopped."), BAD_OP("bo", "Invalid operation."), diff --git a/src/net/socialgamer/cah/data/User.java b/src/net/socialgamer/cah/data/User.java index d3c5193..dd3a659 100644 --- a/src/net/socialgamer/cah/data/User.java +++ b/src/net/socialgamer/cah/data/User.java @@ -30,6 +30,10 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.PriorityBlockingQueue; +import javax.annotation.Nullable; + +import net.socialgamer.cah.db.Account; + /** * A user connected to the server. @@ -52,7 +56,8 @@ public class User { private final String hostName; - private final boolean isAdmin; + @Nullable + private final Account account; private final List lastMessageTimes = Collections.synchronizedList(new LinkedList()); @@ -68,13 +73,13 @@ public class User { * The user's nickname. * @param hostName * The user's Internet hostname (which will likely just be their IP address). - * @param isAdmin - * Whether this user is an admin. + * @param account + * The user's account, if the nickname is registered. */ - public User(final String nickname, final String hostName, final boolean isAdmin) { + public User(final String nickname, final String hostName, @Nullable final Account account) { this.nickname = nickname; this.hostName = hostName; - this.isAdmin = isAdmin; + this.account = account; queuedMessages = new PriorityBlockingQueue(); } @@ -143,7 +148,7 @@ public class User { } public boolean isAdmin() { - return isAdmin; + return null != account && account.isRoot(); } /** diff --git a/src/net/socialgamer/cah/db/Account.java b/src/net/socialgamer/cah/db/Account.java new file mode 100644 index 0000000..907788f --- /dev/null +++ b/src/net/socialgamer/cah/db/Account.java @@ -0,0 +1,216 @@ +/** + * Copyright (c) 2014, Andy Janata + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.socialgamer.cah.db; + +import java.util.Date; + +import javax.annotation.Nonnull; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.Session; + + +/** + * A user account. + * + * @author Andy Janata (ajanata@socialgamer.net) + */ +@Entity +@Table(name = "accounts") +public class Account { + + @Id + @GeneratedValue + private int id; + + // TODO constant this somewhere + @Column(length = 30, nullable = false) + private String username; + @Column(length = 100, nullable = true) + private String email; + private boolean emailVerified; + private boolean lockedOut; + // FIXME don't use plaintext once I have reference material + @Column(length = 30, nullable = false) + private String password; + // TODO tokens for email verification + + /** + * Flag to indicate this well-known name is actually that person. + */ + private boolean verifiedPerson; + /** + * Super flag to say this user can do anything in the system. + */ + private boolean root; + private boolean canKickFromServer; + private boolean canBanFromServer; + private boolean canKickFromAnyGame; + private boolean canIgnoreGamePasswords; + private boolean canIgnoreGameLimits; + private boolean canIgnoreServerLimits; + + @Column(nullable = false) + private Date created; + @Column(nullable = false) + private Date lastSeen; + + public int getId() { + return id; + } + + public void setId(final int id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(final String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(final String email) { + this.email = email; + } + + public boolean isEmailVerified() { + return emailVerified; + } + + public void setEmailVerified(final boolean emailVerified) { + this.emailVerified = emailVerified; + } + + public boolean isLockedOut() { + return lockedOut; + } + + public void setLockedOut(final boolean lockedOut) { + this.lockedOut = lockedOut; + } + + public String getPassword() { + return password; + } + + public void setPassword(final String password) { + this.password = password; + } + + public boolean isVerifiedPerson() { + return verifiedPerson; + } + + public void setVerifiedPerson(final boolean verifiedPerson) { + this.verifiedPerson = verifiedPerson; + } + + public boolean isRoot() { + return root; + } + + public void setRoot(final boolean root) { + this.root = root; + } + + public boolean isCanKickFromServer() { + return canKickFromServer; + } + + public void setCanKickFromServer(final boolean canKickFromServer) { + this.canKickFromServer = canKickFromServer; + } + + public boolean isCanBanFromServer() { + return canBanFromServer; + } + + public void setCanBanFromServer(final boolean canBanFromServer) { + this.canBanFromServer = canBanFromServer; + } + + public boolean isCanKickFromAnyGame() { + return canKickFromAnyGame; + } + + public void setCanKickFromAnyGame(final boolean canKickFromAnyGame) { + this.canKickFromAnyGame = canKickFromAnyGame; + } + + public boolean isCanIgnoreGamePasswords() { + return canIgnoreGamePasswords; + } + + public void setCanIgnoreGamePasswords(final boolean canIgnoreGamePasswords) { + this.canIgnoreGamePasswords = canIgnoreGamePasswords; + } + + public boolean isCanIgnoreGameLimits() { + return canIgnoreGameLimits; + } + + public void setCanIgnoreGameLimits(final boolean canIgnoreGameLimits) { + this.canIgnoreGameLimits = canIgnoreGameLimits; + } + + public boolean isCanIgnoreServerLimits() { + return canIgnoreServerLimits; + } + + public void setCanIgnoreServerLimits(final boolean canIgnoreServerLimits) { + this.canIgnoreServerLimits = canIgnoreServerLimits; + } + + public Date getCreated() { + return created; + } + + public void setCreated(final Date created) { + this.created = created; + } + + public Date getLastSeen() { + return lastSeen; + } + + public void setLastSeen(final Date lastSeen) { + this.lastSeen = lastSeen; + } + + public static Account getAccount(@Nonnull final Session session, + @Nonnull final String username) { + return (Account) session.createQuery("from Account where username = :username") + .setParameter("username", username).uniqueResult(); + } + +} diff --git a/src/net/socialgamer/cah/handlers/RegisterHandler.java b/src/net/socialgamer/cah/handlers/RegisterHandler.java index 540ae73..7965ea9 100644 --- a/src/net/socialgamer/cah/handlers/RegisterHandler.java +++ b/src/net/socialgamer/cah/handlers/RegisterHandler.java @@ -32,7 +32,6 @@ import javax.servlet.http.HttpSession; import net.socialgamer.cah.CahModule.BanList; import net.socialgamer.cah.CahModule.MaxUsers; -import net.socialgamer.cah.Constants; import net.socialgamer.cah.Constants.AjaxOperation; import net.socialgamer.cah.Constants.AjaxRequest; import net.socialgamer.cah.Constants.AjaxResponse; @@ -42,6 +41,9 @@ import net.socialgamer.cah.Constants.SessionAttribute; import net.socialgamer.cah.RequestWrapper; import net.socialgamer.cah.data.ConnectedUsers; import net.socialgamer.cah.data.User; +import net.socialgamer.cah.db.Account; + +import org.hibernate.Session; import com.google.inject.Inject; @@ -60,13 +62,15 @@ public class RegisterHandler extends Handler { private final ConnectedUsers users; private final Set banList; private final Integer maxUsers; + private final Session hibernateSession; @Inject public RegisterHandler(final ConnectedUsers users, @BanList final Set banList, - @MaxUsers final Integer maxUsers) { + @MaxUsers final Integer maxUsers, final Session session) { this.users = users; this.banList = banList; this.maxUsers = maxUsers; + this.hibernateSession = session; } @Override @@ -87,8 +91,22 @@ public class RegisterHandler extends Handler { } else if ("xyzzy".equalsIgnoreCase(nick)) { return error(ErrorCode.RESERVED_NICK); } else { - final User user = new User(nick, request.getRemoteAddr(), - Constants.ADMIN_IP_ADDRESSES.contains(request.getRemoteAddr())); + final String password = request.getParameter(AjaxRequest.PASSWORD); + final Account account = Account.getAccount(hibernateSession, nick); + if (null != account) { + if (null == password || password.isEmpty()) { + return error(ErrorCode.ACCOUNT_IS_REGISTERED); + } else if (!account.getPassword().equals(password)) { + // FIXME hash incoming password + // wrong password + return error(ErrorCode.WRONG_PASSWORD); + } + } else if (null != password && !password.isEmpty()) { + // no account found + return error(ErrorCode.ACCOUNT_NOT_REGISTERED); + } + + final User user = new User(nick, request.getRemoteAddr(), account); final ErrorCode errorCode = users.checkAndAdd(user, maxUsers); if (null == errorCode) { // There is a findbugs warning on this line: