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:
+ Nickname:
+ Password (optional):
+
+
+ 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: