Skip to content

feat: add option to change username #5199

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -501,5 +501,11 @@
"toggle_theme": "Toggle Theme",
"carousel_slide": "Slide {{current}} of {{total}}",
"carousel_skip": "Skip the Carousel",
"carousel_go_to": "Go to slide `x`"
"carousel_go_to": "Go to slide `x`",
"accounts_new_username": "New username",
"accounts_change_username": "Change username",
"accounts_username_required_field": "Username is a required field",
"accounts_username_empty": "Username cannot be empty",
"accounts_username_is_the_same": "This is your username, use another one",
"accounts_username_taken": "Username is already taken, use another one"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd need to use UUIDs of some sort to differenciate accounts at some point. I'm worried that account name switching could be used to find if someone else is registered on this instance.

Copy link
Member Author

@Fijxu Fijxu Apr 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm worried that account name switching could be used to find if someone else is registered on this instance.

That is already possible in the login page, if you insert a registered user with a wrong password it will return Wrong username or password.

Unless you are referring to another case I'm missing

}
37 changes: 37 additions & 0 deletions src/invidious/database/users.cr
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,43 @@ module Invidious::Database::Users
PG_DB.exec(request, pass, user.email)
end

def update_username(user : User, new_username : String)
request = <<-SQL
UPDATE users
SET email = $1
WHERE email = $2
SQL

PG_DB.exec(request, new_username, user.email)
end

def update_user_session_id(user : User, username : String)
request = <<-SQL
UPDATE session_ids
SET email = $1
WHERE email = $2
SQL

PG_DB.exec(request, username, user.email)
end

def update_user_playlists_author(user : User, username : String)
request = <<-SQL
UPDATE playlists
SET author = $1
WHERE author = $2
SQL

PG_DB.exec(request, username, user.email)
end

def update_user_materialized_view(user : User, username : String)
view_name = "public.subscriptions_#{sha256(user.email)}"
new_view_name = "subscriptions_#{sha256(username)}"

PG_DB.exec("ALTER MATERIALIZED VIEW #{view_name} RENAME TO #{new_view_name}")
end

# -------------------
# Select
# -------------------
Expand Down
66 changes: 66 additions & 0 deletions src/invidious/routes/account.cr
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,72 @@ module Invidious::Routes::Account
env.redirect referer
end

# -------------------
# Username update
# -------------------

# Show the username change interface (GET request)
def get_change_username(env)
locale = env.get("preferences").as(Preferences).locale

user = env.get? "user"
sid = env.get? "sid"
referer = get_referer(env)

if !user
return env.redirect referer
end

user = user.as(User)
sid = sid.as(String)
csrf_token = generate_response(sid, {":change_username"}, HMAC_KEY)

templated "user/change_username"
end

# Handle the username change (POST request)
def post_change_username(env)
locale = env.get("preferences").as(Preferences).locale

user = env.get? "user"
sid = env.get? "sid"
referer = get_referer(env)

if !user
return env.redirect referer
end

user = user.as(User)
sid = sid.as(String)
token = env.params.body["csrf_token"]?

begin
validate_request(token, sid, env.request, HMAC_KEY, locale)
rescue ex
return error_template(400, ex)
end

new_username = env.params.body["new_username"]?.try &.downcase.byte_slice(0, 254)
if new_username.nil? || new_username.empty?
return error_template(401, "accounts_username_required_field")
end

if new_username == user.email
return error_template(401, "accounts_username_is_the_same")
end

if Invidious::Database::Users.select(email: new_username)
return error_template(401, "accounts_username_taken")
end

Invidious::Database::Users.update_username(user, new_username.to_s)
Invidious::Database::Users.update_user_session_id(user, new_username.to_s)
Invidious::Database::Users.update_user_playlists_author(user, new_username.to_s)
Invidious::Database::Users.update_user_materialized_view(user, new_username.to_s)

env.redirect referer
end

# -------------------
# Account deletion
# -------------------
Expand Down
2 changes: 2 additions & 0 deletions src/invidious/routing.cr
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ module Invidious::Routing
# User account management
get "/change_password", Routes::Account, :get_change_password
post "/change_password", Routes::Account, :post_change_password
get "/change_username", Routes::Account, :get_change_username
post "/change_username", Routes::Account, :post_change_username
get "/delete_account", Routes::Account, :get_delete
post "/delete_account", Routes::Account, :post_delete
get "/clear_watch_history", Routes::Account, :get_clear_history
Expand Down
26 changes: 26 additions & 0 deletions src/invidious/views/user/change_username.ecr
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<% content_for "header" do %>
<title><%= translate(locale, "accounts_change_username") %> - Invidious</title>
<% end %>

<div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-5"></div>
<div class="pure-u-1 pure-u-lg-3-5">
<div class="h-box">
<form class="pure-form pure-form-aligned" action="/change_username?referer=<%= URI.encode_www_form(referer) %>" method="post">
<legend><%= translate(locale, "") %></legend>

<fieldset>
<label for="accounts_new_username"><%= translate(locale, "accounts_new_username") %> :</label>
<input required class="pure-input-1" name="new_username" type="text" placeholder="<%= translate(locale, "new_username") %>">

<button type="submit" name="action" value="change_username" class="pure-button pure-button-primary">
<%= translate(locale, "accounts_change_username") %>
</button>

<input type="hidden" name="csrf_token" value="<%= HTML.escape(csrf_token) %>">
</fieldset>
</form>
</div>
</div>
<div class="pure-u-1 pure-u-lg-1-5"></div>
</div>
4 changes: 4 additions & 0 deletions src/invidious/views/user/preferences.ecr
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,10 @@
<a href="/change_password?referer=<%= URI.encode_www_form(referer) %>"><%= translate(locale, "Change password") %></a>
</div>

<div class="pure-control-group">
<a href="/change_username?referer=<%= URI.encode_www_form(referer) %>"><%= translate(locale, "accounts_change_username") %></a>
</div>

<div class="pure-control-group">
<a href="/data_control?referer=<%= URI.encode_www_form(referer) %>"><%= translate(locale, "Import/export data") %></a>
</div>
Expand Down