account management; change username & password
This commit is contained in:
parent
8aef039d01
commit
7a5bb92cc8
|
@ -108,4 +108,8 @@ pre {
|
|||
|
||||
.fancy-button:hover {
|
||||
background-color: var(--accent-color-bri);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #f33;
|
||||
}
|
|
@ -12,14 +12,101 @@
|
|||
margin: auto;
|
||||
padding: 1em;
|
||||
}
|
||||
.greeting ::selection {
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
}
|
||||
.greeting {
|
||||
background-color: var(--accent-color);
|
||||
color: #000;
|
||||
border-radius: 1.5em;
|
||||
padding: 1em;
|
||||
margin: 1em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
}
|
||||
.greeting-l {
|
||||
flex: 0 0 auto;
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
height: 100%;
|
||||
max-height: 3rem;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.greeting-r {
|
||||
flex: 1 1 0px;
|
||||
min-width: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.greeting-top {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.greeting-top-left {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
.greeting-stats {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
gap: 0.5ex;
|
||||
}
|
||||
.greeting-stats img {
|
||||
width: auto;
|
||||
height: 1em;
|
||||
}
|
||||
.greeting a {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
@media (max-width: 650px) {
|
||||
.greeting-top {
|
||||
flex-direction: column;
|
||||
gap: 0.2em;
|
||||
align-items: start;
|
||||
}
|
||||
.greeting-bottom {
|
||||
text-align: right;
|
||||
}
|
||||
.greeting-r {
|
||||
gap: 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.favicon {
|
||||
margin: auto;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
hewwo, <b><%= username %></b>!<br><br>
|
||||
<el>
|
||||
<li>Nothing is implemented right now for account management</li>
|
||||
<li>This is just a test list</li>
|
||||
<li>Did you know your account ID is <%= account_id %>?</li>
|
||||
</el>
|
||||
<a href="/"><img src="/favicon.png" width="64" height="auto" class="spinny favicon"></a><br>
|
||||
|
||||
<div class="greeting">
|
||||
<img src="https://cdn.discordapp.com/attachments/902195395264905217/1062706739969019984/ball_35.png" width="150" height="150" class="greeting-l">
|
||||
<div class="greeting-r">
|
||||
<div class="greeting-top">
|
||||
<div class="greeting-top-left">
|
||||
hewwo, <b><%= username %></b>!
|
||||
</div>
|
||||
<div class="greeting-stats">
|
||||
<%= stars %> <img src="https://gdbrowser.com/assets/star.png"> <%= diamonds %> <img src="https://gdbrowser.com/assets/diamond.png"> <%= coins %> <img src="https://gdbrowser.com/assets/coin.png"> <%= user_coins %> <img src="https://gdbrowser.com/assets/silvercoin.png"> <%= demons %> <img src="https://gdbrowser.com/assets/demon.png">
|
||||
</div>
|
||||
</div>
|
||||
<div class="greeting-bottom">
|
||||
<a href="/accounts/settings">Settings</a> · <a>Log out</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br><br>
|
||||
blablabla lorem ipsum whatever. put stuff here later
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
<title>Account Management</title>
|
||||
<style>
|
||||
body {
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
padding: 1em;
|
||||
}
|
||||
form {
|
||||
margin: auto;
|
||||
max-width: 700px;
|
||||
background-color: rgba(150, 150, 150, 0.15);
|
||||
padding: 1em;
|
||||
border-radius: 1em;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%= Templates.dir_header %><br>
|
||||
|
||||
<%- if error -%>
|
||||
<div class="error"><%= error %></div>
|
||||
<br>
|
||||
<%- elsif result -%>
|
||||
<%= result %>
|
||||
<br><br>
|
||||
<%- end -%>
|
||||
|
||||
<form action="/accounts/settings" method="post">
|
||||
<label for="username">Username</label><br>
|
||||
<input type="text" id="username" name="username" minlength="3" maxlength="16" required value="<%= username %>" /><br>
|
||||
<hr>
|
||||
<input type="submit" value="Update Account" />
|
||||
</form>
|
||||
|
||||
<br>
|
||||
|
||||
<form action="/accounts/settings" method="post">
|
||||
<label for="old_password">Old password</label><br>
|
||||
<input type="password" id="old_password" name="old_password" minlength="3" maxlength="16" required/><br>
|
||||
<label for="new_password">New password</label><br>
|
||||
<input type="password" id="new_password" name="new_password" minlength="3" maxlength="16" required/><br>
|
||||
<label for="repeat_new_password">Repeat new password</label><br>
|
||||
<input type="password" id="repeat_new_password" name="repeat_new_password" minlength="3" maxlength="16" required/><br>
|
||||
<hr>
|
||||
<input type="submit" value="Update Password" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
|
@ -36,6 +36,7 @@
|
|||
But you probably already knew that. You may be looking for:<br>
|
||||
<el>
|
||||
<li>The <a href="https://git.oat.zone/oat/crystal-gauntlet">Git repository</a></li>
|
||||
<li><a href="/accounts">Account stuff</a></li>
|
||||
<li><a href="/tools/song_upload">Song reuploading</a> and <a href="/tools/song_search">searching</a></li>
|
||||
<li><a href="/tools/levels">Levels</a> and <a href="/tools/reupload">level reuploading</a></li>
|
||||
<%- if config_get("sessions.allow").as(Bool | Nil) -%>
|
||||
|
|
|
@ -27,13 +27,10 @@
|
|||
height: 100%;
|
||||
padding: 1em;
|
||||
}
|
||||
.error {
|
||||
color: #f33;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/accounts/" method="post">
|
||||
<form method="post">
|
||||
<img src="/favicon.png" width="64" height="auto" class="spinny">
|
||||
<br>
|
||||
<label for="username">Username</label>
|
||||
|
|
|
@ -4,8 +4,8 @@ module CrystalGauntlet::Templates
|
|||
extend self
|
||||
|
||||
macro dir_header()
|
||||
path_split = context.request.path.split('/')
|
||||
"<div class='dir-header'>" + path_split.map_with_index { |v, i| "<a href='/#{path_split[1..i].join('/')}'>#{i == 0 ? "crystal-gauntlet" : v}</a>"}.join(" / ") + "</div>"
|
||||
%path_split = context.request.path.split('/')
|
||||
"<div class='dir-header'>" + %path_split.map_with_index { |v, i| "<a href='/#{%path_split[1..i].join('/')}'>#{i == 0 ? "crystal-gauntlet" : v}</a>"}.join(" / ") + "</div>"
|
||||
end
|
||||
|
||||
def footer()
|
||||
|
@ -15,6 +15,26 @@ module CrystalGauntlet::Templates
|
|||
</div>
|
||||
)
|
||||
end
|
||||
|
||||
macro auth()
|
||||
if session = CrystalGauntlet.sessions.get(context)
|
||||
logged_in = true
|
||||
account_id = session.account_id
|
||||
user_id = session.user_id
|
||||
username = session.username
|
||||
else
|
||||
logged_in = false
|
||||
account_id = nil
|
||||
user_id = nil
|
||||
username = nil
|
||||
end
|
||||
|
||||
if !logged_in
|
||||
context.response.headers.add("Location", "/login?#{URI::Params.encode({"redir" => context.request.path})}")
|
||||
context.response.status = HTTP::Status::SEE_OTHER
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module CrystalGauntlet
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
require "uri"
|
||||
require "http-session"
|
||||
|
||||
include CrystalGauntlet
|
||||
|
||||
CrystalGauntlet.template_endpoints["/#{config_get("general.append_path").as(String | Nil) || ""}accounts/accountManagement.php"] = ->(context : HTTP::Server::Context) {
|
||||
|
@ -11,54 +8,11 @@ CrystalGauntlet.template_endpoints["/#{config_get("general.append_path").as(Stri
|
|||
CrystalGauntlet.template_endpoints["/accounts"] = ->(context : HTTP::Server::Context) {
|
||||
context.response.content_type = "text/html"
|
||||
|
||||
if session = CrystalGauntlet.sessions.get(context)
|
||||
logged_in = true
|
||||
account_id = session.account_id
|
||||
user_id = session.user_id
|
||||
username = session.username
|
||||
else
|
||||
logged_in = false
|
||||
account_id = nil
|
||||
user_id = nil
|
||||
username = nil
|
||||
end
|
||||
user_id = nil
|
||||
username = nil
|
||||
Templates.auth()
|
||||
|
||||
body = context.request.body
|
||||
if body
|
||||
begin
|
||||
params = URI::Params.parse(body.gets_to_end)
|
||||
username = params["username"].strip
|
||||
password = params["password"].strip
|
||||
stars, demons, coins, user_coins, diamonds, creator_points = DATABASE.query_one("select stars, demons, coins, user_coins, diamonds, creator_points from users where id = ?", user_id, as: {Int32, Int32, Int32, Int32, Int32, Int32})
|
||||
|
||||
if username.empty? || password.empty?
|
||||
raise "Invalid username or password"
|
||||
end
|
||||
|
||||
# todo: dedup this code with the login account endpoint maybe
|
||||
result = DATABASE.query_all("select id, password from accounts where username = ?", username, as: {Int32, String})
|
||||
if result.size > 0
|
||||
account_id, hash = result[0]
|
||||
bcrypt = Crypto::Bcrypt::Password.new(hash)
|
||||
|
||||
if bcrypt.verify(password)
|
||||
user_id = Accounts.get_user_id(account_id)
|
||||
logged_in = true
|
||||
LOG.debug { "#{username} logged in" }
|
||||
CrystalGauntlet.sessions.set(context, UserSession.new(username, account_id, user_id))
|
||||
else
|
||||
raise "Invalid password"
|
||||
end
|
||||
else
|
||||
raise "No such user exists"
|
||||
end
|
||||
rescue error
|
||||
LOG.error(exception: error) {"whar...."}
|
||||
end
|
||||
end
|
||||
|
||||
if logged_in
|
||||
ECR.embed("./public/template/account_management.ecr", context.response)
|
||||
else
|
||||
ECR.embed("./public/template/login.ecr", context.response)
|
||||
end
|
||||
ECR.embed("./public/template/account_management.ecr", context.response)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
require "uri"
|
||||
|
||||
include CrystalGauntlet
|
||||
|
||||
CrystalGauntlet.template_endpoints["/accounts/settings"] = ->(context : HTTP::Server::Context) {
|
||||
context.response.content_type = "text/html"
|
||||
|
||||
account_id = nil
|
||||
user_id = nil
|
||||
username = nil
|
||||
|
||||
Templates.auth()
|
||||
|
||||
result = nil
|
||||
|
||||
params = context.request.body.try { |b| URI::Params.parse(b.gets_to_end) }
|
||||
if params
|
||||
begin
|
||||
if params["username"]?
|
||||
# todo: dedup this and the gd register endpoint
|
||||
username = params["username"].strip
|
||||
if username.size < 3
|
||||
raise "Username must at least be 3 characters long"
|
||||
end
|
||||
if username.size > 16
|
||||
raise "Username must at most be 16 characters long"
|
||||
end
|
||||
|
||||
if DATABASE.scalar("select count(*) from accounts where username = ?", username).as(Int64) > 0
|
||||
raise "Username already taken"
|
||||
end
|
||||
|
||||
DATABASE.exec("update accounts set username = ? where id = ?", username, account_id)
|
||||
DATABASE.exec("update users set username = ? where id = ?", username, user_id)
|
||||
|
||||
# refresh session
|
||||
CrystalGauntlet.sessions.set(context, UserSession.new(username, account_id.not_nil!, user_id.not_nil!))
|
||||
|
||||
result = "Changed username successfully"
|
||||
end
|
||||
|
||||
if params["old_password"]? && params["new_password"]? && params["repeat_new_password"]?
|
||||
if params["repeat_new_password"] != params["new_password"]
|
||||
raise "New password and repeated password do not match"
|
||||
end
|
||||
|
||||
new_password = params["new_password"].strip
|
||||
|
||||
# todo: dedup this and gd register endpoint
|
||||
if new_password.size < 6
|
||||
raise "New password must be at least 6 characters long"
|
||||
end
|
||||
|
||||
old_hash = DATABASE.query_one("select password from accounts where username = ?", username, as: {String})
|
||||
bcrypt = Crypto::Bcrypt::Password.new(old_hash)
|
||||
|
||||
if !bcrypt.verify(params["old_password"])
|
||||
raise "Invalid old password"
|
||||
end
|
||||
|
||||
password_hash = Crypto::Bcrypt::Password.create(new_password, cost: 10).to_s
|
||||
gjp2 = CrystalGauntlet::GJP.hash(new_password)
|
||||
DATABASE.exec("update accounts set password = ?, gjp2 = ? where id = ?", password_hash, gjp2, account_id)
|
||||
|
||||
result = "Changed password successfully"
|
||||
end
|
||||
rescue error
|
||||
LOG.error {"whar.... #{error}"}
|
||||
end
|
||||
end
|
||||
|
||||
ECR.embed("./public/template/account_settings.ecr", context.response)
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
require "uri"
|
||||
require "http-session"
|
||||
|
||||
include CrystalGauntlet
|
||||
|
||||
CrystalGauntlet.template_endpoints["/login"] = ->(context : HTTP::Server::Context) {
|
||||
if session = CrystalGauntlet.sessions.get(context)
|
||||
logged_in = true
|
||||
account_id = session.account_id
|
||||
user_id = session.user_id
|
||||
username = session.username
|
||||
else
|
||||
logged_in = false
|
||||
account_id = nil
|
||||
user_id = nil
|
||||
username = nil
|
||||
end
|
||||
|
||||
body = context.request.body
|
||||
if body
|
||||
begin
|
||||
params = URI::Params.parse(body.gets_to_end)
|
||||
username = params["username"].strip
|
||||
password = params["password"].strip
|
||||
|
||||
if username.empty? || password.empty?
|
||||
raise "Invalid username or password"
|
||||
end
|
||||
|
||||
# todo: dedup this code with the login account endpoint maybe
|
||||
result = DATABASE.query_all("select id, password from accounts where username = ?", username, as: {Int32, String})
|
||||
if result.size > 0
|
||||
account_id, hash = result[0]
|
||||
bcrypt = Crypto::Bcrypt::Password.new(hash)
|
||||
|
||||
if bcrypt.verify(password)
|
||||
user_id = Accounts.get_user_id(account_id)
|
||||
logged_in = true
|
||||
LOG.debug { "#{username} logged in" }
|
||||
CrystalGauntlet.sessions.set(context, UserSession.new(username, account_id, user_id))
|
||||
else
|
||||
raise "Invalid password"
|
||||
end
|
||||
else
|
||||
raise "No such user exists"
|
||||
end
|
||||
rescue error
|
||||
LOG.error(exception: error) {"whar...."}
|
||||
end
|
||||
end
|
||||
|
||||
if logged_in
|
||||
context.response.headers.add("Location", "#{context.request.query_params["redir"]? || "/accounts"}")
|
||||
context.response.status = HTTP::Status::SEE_OTHER
|
||||
return
|
||||
else
|
||||
ECR.embed("./public/template/login.ecr", context.response)
|
||||
end
|
||||
}
|
Loading…
Reference in New Issue