diff --git a/locales/en-US.json b/locales/en-US.json index 4f2c2770..44d4c5ef 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -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`", + "new_username": "New username", + "change_username": "Change username", + "username_required_field": "Username is a required field", + "username_empty": "Username cannot be empty", + "username_is_the_same": "This is your username, use another one", + "username_taken": "Username is already taken, use another one" } diff --git a/src/invidious/database/users.cr b/src/invidious/database/users.cr index 4a3056ea..bf54a321 100644 --- a/src/invidious/database/users.cr +++ b/src/invidious/database/users.cr @@ -184,6 +184,43 @@ module Invidious::Database::Users PG_DB.exec(request, pass, user.email) end + def update_username(user : User, username : String) + request = <<-SQL + UPDATE users + SET email = $1 + WHERE email = $2 + SQL + + PG_DB.exec(request, 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 # ------------------- diff --git a/src/invidious/routes/account.cr b/src/invidious/routes/account.cr index c8db207c..4eff5e1d 100644 --- a/src/invidious/routes/account.cr +++ b/src/invidious/routes/account.cr @@ -78,6 +78,76 @@ 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? + return error_template(401, "username_required_field") + end + + if new_username.empty? + return error_template(401, "username_empty") + end + + if new_username == user.email + return error_template(401, "username_is_the_same") + end + + if Invidious::Database::Users.select(email: new_username) + return error_template(401, "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 # ------------------- diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr index 46b71f1f..38ddb839 100644 --- a/src/invidious/routing.cr +++ b/src/invidious/routing.cr @@ -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 diff --git a/src/invidious/views/user/change_username.ecr b/src/invidious/views/user/change_username.ecr new file mode 100644 index 00000000..85af79e6 --- /dev/null +++ b/src/invidious/views/user/change_username.ecr @@ -0,0 +1,26 @@ +<% content_for "header" do %> +<%= translate(locale, "change_username") %> - Invidious +<% end %> + +
+
+
+
+
+ <%= translate(locale, "") %> + +
+ + "> + + + + +
+
+
+
+
+
diff --git a/src/invidious/views/user/preferences.ecr b/src/invidious/views/user/preferences.ecr index cf8b5593..5cea8164 100644 --- a/src/invidious/views/user/preferences.ecr +++ b/src/invidious/views/user/preferences.ecr @@ -330,6 +330,10 @@ <%= translate(locale, "Change password") %> +
+ <%= translate(locale, "change_username") %> +
+
<%= translate(locale, "Import/export data") %>